学习任何一种框架,从一个 Hello World 应用开始是最快了解该框架特性的途径,我们先从一 段简单的 HTML 代码开始,感受 Vue.js 最核心的功能。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Vue 示例</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <input type="text" v-model="name" placeholder="请填写您的名字..." /> <h1>您好,<span style="color:red">{{ name }}</span></h1> </div> </body> <script> var vm = new Vue({ el: '#app', data: { name:'' } }); </script> </html>
这是一段简单到不能再简单的代码,但却展示出了 Vue.js 最核心的功能: 数据的双向绑定。 在输入框输入的内容会实时展示在页面的 h1 标签内,如图所示。
Vue 实例与数据绑定
- 实例与数据
Vue.js 应用的创建很简单,通过构造函数 Vue 就可以创建一个 Vue 的根实例,并启动 Vue 应用:
var app = new Vue({ //选项 })
变量 app 就代表了这个 Vue 实例。事实上,几乎所有的代码都是一个对象,写入 Vue 实例的选项内的。
首先,必不可少的一个选项就是 el。 el 用于指定一个页面中己存在的 DOM 元素来挂载 Vue 实例,它可以是 HTMLElement ,也可以是 css 选择器,比如:<body> <div id="app"> </div> </body> <script> var vm = new Vue({ el:document.getElementById('app') //或者是'#app' }); </script>
- 挂载成功后,我们可以通过 app.$el 来访问该元素。 Vue 提供了很多常用的实例属性与方法, 都以$开头,比如$el, 后续还会介绍更多有用的方法。
- 回顾章节开始的 Hello World 代码,在 input 标签上,有一个 v-model 的指令,它的值对应于我们创建的 Vue 实例的 data 选项中的 name 字段, 这就是 Vue 的数据绑定。
- 通过 Vue 实例的 data 选项,可以声明应用内需要双向绑定的数据。建议所有会用到的数据都预先在 data 内声明,这样不至于将数据散落在业务逻辑中,难以维护。 Vue 实例本身也代理了 data 对象里的所有属性,所以可以这样访问:
var vm = new Vue({ el:'#app', data:{ a:2 } }); console.log(vm.a);
- 除了显式地声明数据外,也可以指向一个己有的变量,并且它们之间默认建立了双向绑定, 当修改其中任意一个时,另一个也会一起变化:
<script> var myData = { a: 1 } var vm = new Vue({ el: '#app', data: myData }); console.log(vm.a);//1 //修改属性,原数据也会随之修改 vm.a = 2; console.log(myData.a);//2 //反之,修改是原数据,Vue属性也会修改 myData.a = 3; console.log(vm.a);//3 </script>
- 生命周期
每个 Vue 实例创建时,都会经历一系列的初始化过程,同时也会调用相应的生命周期钩子, 我们可以利用这些钩子,在合适的时机执行我们的业务逻辑。如果你使用过 jQuery, 一定知道它的 ready() 方法,比如以下示例:
$(document).ready(function(){ // DOM 加载完后,会执行这里面的代码 });
Vue 的生命周期钩子与之类似,比较常用的有:
- created: 实例创建完成后调用,此阶段完成了数据的观测等,但尚未挂载, $el 还不可用。
需要初始化处理一些数据时会比较有用,后面章节将有介绍。- mounted: el 挂载到实例上后调用,一般我们的第一个业务逻辑会在这里开始。
- beforeDestroy: 实例销毁之前调用。主要解绑一些使用 addEventListener 监听的事件等。
这些钩子与 el 和 data 类似,也是作为选项写入 Vue 实例内,并且钩子的 this 指向的是调用它的 Vue 实例:
var vm = new Vue({ el: '#app', data: { a : 666 }, created: function () { console.log(this.a);//2 }, mounted: function () { console.log(this.$el);//<div id="app"></div> } });
- 插值与表达式
使用双大括号(Mustache 语法)“{{}}”是最基本的文本插值方法,它会自动将我们双向绑定的数据实时显示出来,例如:
<body> <div id="app"> {{ book }} </div> </body> <script> var vm = new Vue({ el: '#app', data: { book: '《Vue.js 实战》' } }); </script>
大括号里的内容会被替换为《Vue.js 实战》,通过任何方法修改数据 book,大括号的内容都会被实时替换,比如下面的这个示例,实时显示当前的时间,每秒更新:
<body> <div id="app"> {{ date }} </div> </body> <script> var vm = new Vue({ el: '#app', data: { date: new Date() }, mounted: function(){ //声明一个变量指向 Vue 实例 this.作用域一致 var _this = this; this.timer = setInterval(function(){ //修改数据date _this.date = new Date(); },1000); }, beforeDestroy: function(){ if(this.timer){ //在 Vue 实例销毁前,清除我们的定时器 clearInterval(); } } }); </script>
这里的{{ date } }输出的是浏览器默认的时间格式,如下所示,所以在这里需要注意时区。有多种方法可以对时间格式化,比如赋值前先使用自定义的函数处理。 Vue 的过滤器( filter )或计算属性 (computed )也可以实现,稍后会介绍到 。
如果有的时候就是想输出 HTML,而不是将数据解释后的纯文本,可以使用 v-html:
<body> <div id="app"> <span v-html="link"></span> </div> </body> <script> var vm = new Vue({ el: '#app', data: { link: '<a href="#">我是一个连接,可以点我哈</a>' } }); </script>
link 的内容将会被渲染为一个具有点击功能的 a 标签,而不是纯文本。这里要注意,如果将用户产生的内容使用 v-html 输出后,有可能导致 xss 攻击,所以要在服务端对用户提交的内容进行处理, 一般可将尖括号“< >”转义。
如果想显示{{}}标签,而不进行替换, 使用 v-pre 即可跳过这个元素和它的子元素的编译过程, 例如:
<span v-pre>{{这里的内容是不会被编译的门}}</ span>
在{{}}中,除了简单的绑定属性值外,还可以使用 JavaScript 表达式进行简单的运算、 三元运算等,例如:<body> <div id="app"> {{ number / 10 }} {{ isOK ? '确定' : '取消' }} {{ text.split(',').reverse().join(',') }} </div> </body> <script> var vm = new Vue({ el: '#app', data: { number: 100, isOK: false, text: '123,456' } }); </script>
显示结果依次为: 10、取消、 456,123。
Vue.js 只支持单个表达式,不支持语句和流控制。另外, 在表达式中,不能使用用户自定义的全局变量, 只能使用 Vue 白名单内的全局变量, 例如 Math 和 Date。 以下是一些无效的示例:<!--这是语旬,不是表达式--> {{ var book = 'Vue.js 实战' }} <!--不能使用流控制,要使用三元运算--> {{ if(ok) return msg }}
- 过滤器
Vue. 支持在{{}}插值的尾部添加一个管道符 “ ( | )” 对数据进行过滤,经常用于格式化文本,比如字母全部大写、货币千位使用逗号分隔等。过滤的规则是自定义的, 通过给 Vue 实例添加选项 filters 来设置, 例如在上一节中实时显示当前时间的示例,可以对时间进行格式化处理:
<body> <div id="app"> {{ date | formatDate }} </div> </body> <script> //在月份、日期、小时等小于10时前面补0 var padDate = function(value){ return value < 10 ? '0' + value : value; }; var vm = new Vue({ el: '#app', data: { date: new Date() }, filters: { //这里的value就是要过滤的数据 formatDate: function(value){ var date = new Date(value); var year = date.getFullYear(); var month = padDate(date.getMonth() + 1); var day = padDate(date.getDate()); var hours = padDate(date.getDate()); var minutes = padDate(date.getHours()); var seconds = padDate(date.getSeconds()); //将整理好的数据返回出去 return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds; } }, mounted: function(){ //声明一个变量指向 Vue 实例 this.作用域一致 var _this = this; this.timer = setInterval(function(){ //修改数据date _this.date = new Date(); },1000); }, beforeDestroy: function(){ if(this.timer){ //在 Vue 实例销毁前,清除我们的定时器 clearInterval(); } } }); </script>
过滤器也可以串联,而且可以接收参数,例如:
<!--串联--> {{ message | filterA | filterB }} <!--接收参数--> {{ date | filterA('arg1','arg2') }}
这里的字符串arg1 和 arg2 将分别传给过滤器的第二个和第三个参数,因为第一个是数据本身。
过滤器应当用于处理简单的文本转换,如果要实现更为复杂的数据变换,应该使用计 算属性,下一章中会详细介绍它的用法。
指令与事件
指令(Directives)是 Vue.js 模板中最常用的一项功能,它带有前缀 v-, 在前文我们已经使用过不少指令了,比如 v-if、 v-html、 v-pre 等。指令的主要职责就是当其表达式的值改变时,相应地将某些行为应用到 DOM 上,以 v-if 例:
<body> <div id="app"> <p v-if="show">显示这段文本</p> </div> </body> <script> var vm = new Vue({ el: '#app', data: { show: true } }) </script>
当数据 show 的值为 true 时, p 元素会被插入,为 false 时则会被移除。数据驱动 DOM 是 Vue.js 的核心理念,所以不到万不得已时不要主动操作 DOM,你只需要维护好数据, DOM 的事 Vue 会帮你优雅的处理。
Vue.js 内置了很多指令,帮助我们快速完成常见的 DOM 操作,比如循环渲染、显示与隐藏等。 在后面章节中会详细地介绍这些内置指令,但在此之前,你需要先知道 v-bind 和 v-on。 v-bind 的基本用途是动态更新 HTML 元素上的属性,比如 id、 class 等,例如下面几个示例:<body> <div id="app"> <a v-bind:href="url">链接</a> <img v-bind:src="imgUrl"></img> </div> </body> <script> var vm = new Vue({ el: '#app', data: { url: 'https://www.baidu.com', imgUrl: 'img/1.jpg' } }) </script>
- 示例中的链接地址与图片的地址都与数据进行了绑定,当通过各种方式改变数据时,链接和图片都会自动更新。上述示例渲染后的结果为:
<a href="https://www.baidu.com">链接</a>
<img src="img/1.jpg"></img>- 以上是介绍 v-bind 最基本的用法,它在 Vue.js 组件中还有着重要的作用,将在后面章节中详细介绍。 另一个非常重要的指令就是 v-on,它用来绑定事件监听器,这样我们就可以做一些交互了, 先来看下面的示例:
<body> <div id="app"> <p v-if="show">这是一段文本</p> <button v-on:click="handleClose">点击隐藏</button> </div> </body> <script> var vm = new Vue({ el: '#app', data: { show: true }, methods: { handleClose: function(){ this.show = false; } } }) </script>
在 button 按钮上,使用 v-on:click 给该元素绑定了一个点击事件,在普通元素上, v-on 可以监听原生的 DOM 事件,除了 click 外,还有 dblclick、 keyup, mousemove 等。表达式可以是一个方法名,这些方法都写在 Vue 实例的 methods 属性内,并且是函数的形式,函数内的 this 指向的是当前 Vue 实例本身,因此可以直接使用 this.xxx 的形式来访问或修改数据,如示例中的 this.show = false;把数据 show 修改为了 false,所以点击按钮时,文本 p 元素就被移除了。 表达式除了方法名, 也可以直接是一个内联语句,上例也可以改写为:
<body> <div id="app"> <p v-if="show">这是一段文本</p> <button v-on:click="show = false">点击隐藏</button> </div> </body> <script> var vm = new Vue({ el: '#app', data: { show: true } }) </script>
如果绑定的事件要处理复杂的业务逻辑,建议还是在 methods 里声明一个方法,这样可读性更强也好维护。
Vue.js 将 methods 里的方法也代理了,所以也可以像访问 Vue 数据那样来调用方法:<body> <div id="app"> <p v-if="show">这是一段文本</p> <button v-on:click="handleClose">点击隐藏</button> </div> </body> <script> var vm = new Vue({ el: '#app', data: { show: true }, methods: { handleClose: function(){ this.close(); }, close: function(){ this.show = false; } } }) </script>
在 handleClose 方法内,直接通过 this.close() 调用了 close() 函数。在上面示例中是多此一举的, 只是用于演示它的用法,在业务中会经常用到,例如以下几种用法都是正确的:
<script> var vm = new Vue({ el: '#app', data: { show: true }, methods: { init: function(text){ console.log(text); } }, mounted: function(){ this.init('在初始化时调用');//在初始化时调用 } }); vm.init('通过外部调用');//在 Vue 实例外部调用 </script>
更多关于 v-on 事件的用法将会在后面章节中详细介绍。
语法糖
语法糖是指在不影响功能的情况下, 添加某种方法实现同样的效果, 从而方便程序开发。
Vue.js 的 v-bind 和 v-on 指令都提供了语法糖, 也可以说是缩写, 比如 v-bind, 可以省略 v-bind, 直接写一个冒号 “:”<a v-bind:href="url">链接</a> <img v-bind:src="imgUrl"></img> <!--缩写为--> <a :href="url">链接</a> <img :src="imgUrl"></img>
v-on 可以直接用 “@” 来缩写:
<button v-on:click="handleClose">点击隐藏</button> <!--缩写为--> <button @click="handleClose">点击隐藏</button>
使用语法糖可以简化代码的书写, 从下一章开始, 所有示例的 v-bind 和 v-on 指令将默认使用语法糖的写法。