2.1 实例及选项
从以前的例子可以看出,Vue.js的使用都是通过构造函数Vue({option})创建一个Vue的实例:var vm = new Vue({})。一个Vue实例相当于一个MVVM模式中的ViewModel,如图2-1所示。
图2-1
在实例化的时候,我们可以传入一个选项对象,包含数据、模板、挂载元素、方法、生命周期钩子等选项。下面就对一些常用的选项对象属性进行具体的说明。
2.1.1 模板
选项中主要影响模板或DOM的选项有el和template,属性replace和template需要一起使用。
el:类型为字符串,DOM元素或函数。其作用是为实例提供挂载元素。一般来说我们会使用css选择符,或者原生的DOM元素。例如el:'#app'。在初始项中指定了el,实例将立即进入编译过程。
template:类型为字符串。默认会将template值替换挂载元素(即el值对应的元素),并合并挂载元素和模板根节点的属性(如果属性具有唯一性,类似id,则以模板根节点为准)。如果replace为false,模板template的值将插入挂载元素内。通过template插入模板的时候,挂载元素的内容都将被互联,除非使用slot进行分发(有关slot内容将在第6章组件中介绍)。在使用template时,我们往往不会把所有的HTML字符串直接写在js里面,这样影响可读性而且也不利于维护。所以经常用’#tpl’的方式赋值,并且在body内容添加<scrip id="tpl"type="x-template">为标签包含的HTML内容,这样就能将HTML从js中分离开来,示例如下:
<div id="app"> <p>123</p> </div> <script id="tpl" type="x-template"> <div class='tpl'> <p>This is a tpl from script tag</p> </div> </script> <script type="text/javascript"> var vm = new Vue({ el : '#app', template : '#tpl' }); </script>
最终输出HTML结构为:
Vue.js 2.0中废除了replace这个参数,并且强制要求每一个Vue.js实例需要有一个根元素,即不允许组件模板为:
<script id="tpl" type="x-template"> <div class='tpl'> … </div> <div class='tpl'> … </div> </script>
这样的兄弟节点为根节点的模板形式,需要改写成:
<script id="tpl" type="x-template"> <div class='wrapper'> <div class='tpl'> … </div> <div class='tpl'> … </div> </div> </script>
2.1.2 数据
Vue.js实例中可以通过data属性定义数据,这些数据可以在实例对应的模板中进行绑定并使用。需要注意的是,如果传入data的是一个对象,Vue实例会代理起data对象里的所有属性,而不会对传入的对象进行深拷贝。另外,我们也可以引用Vue实例vm中的$data来获取声明的数据,例如:
var data = { a: 1 } var vm = new Vue({ data: data }) vm.$data === data // -> true vm.a === data.a // -> true // 设置属性也会影响到原始数据 vm.a = 2 data.a // -> 2 // 反之亦然 data.a = 3 vm.a // -> 3
然后在模板中使用{{a}}就会输出vm.a的值,并且修改vm.a的值,模板中的值会随之改变,我们也会称这个数据为响应式(responsive)数据(具体的用法和特性会在第2.2节的数据绑定中说明)。
需要注意的是,只有初始化时传入的对象才是响应式的,即在声明完实例后,再加上一句vm.$data.b = '2',并在模板中使用{{b}},这时是不会输出字符串’2’的。例如:
<div id="app"> <p>{{a}}</p> <p>{{b}}</p> </div> var vm = new Vue({ el : '#app', data : { a : 1 } }); vm.$data.b = 2;
如果需要在实例化之后加入响应式变量,需要调用实例方法$set,例如:
vm.$set('b', 2);
不过Vue.js并不推荐这么做,这样会抛出一个异常:
所以,我们应尽量在初始化的时候,把所有的变量都设定好,如果没有值,也可以用undefined或null占位。
另外,组件类型的实例可以通过props获取数据,同data一样,也需要在初始化时预设好。示例:
<my-component title='myTitle' content='myContent'></my-component> var myComponent = Vue.component('my-component', { props : ['title', 'content'], template : '<h1>{{title}}</h1><p>{{content}}</p>' })
我们也可以在上述组件类型实例中同时使用data,但有两个地方需要注意:① data的值必须是一个函数,并且返回值是原始对象。如果传给组件的data是一个原始对象的话,则在建立多个组件实例时它们就会共用这个data对象,修改其中一个组件实例的数据就会影响到其他组件实例的数据,这显然不是我们所期望的。② data中的属性和props中的不能重名。这两者均会抛出异常:
所以正确的使用方法如下:
var MyComponent = Vue.component('my-component', { props : ['title', 'content'], data : function() { return { desc : '123' } }, template : '<div> \ <h1>{{title}}</h1> \ <p>{{content}}</p> \ <p>{{desc}}</p> \ </div>' })
2.1.3 方法
我们可以通过选项属性methods对象来定义方法,并且使用v-on指令来监听DOM事件,例如:
<button v-on:click="alert"/>alert<button> new Vue({ el : '#app', data : { a : 1}, methods : { alert : function() { alert(this.a); } } });
另外,Vue.js实例也支持自定义事件,可以在初始化时传入events对象,通过实例的$emit方法进行触发。这套通信机制常用在组件间相互通信的情况中,例如子组件冒泡触发父组件事件方法,或者父组件广播某个事件,子组件对其进行监听等。这里先简单说明下用法,详细的情况将会在第6章组件中进行说明。
var vm = new Vue({ el : '#app', data : data, events : { 'event.alert' : function() { alert('this is event alert :' + this.a); } } }); vm.$emit('event.alert');
而Vue.js 2.0中废弃了events选项属性,不再支持事件广播这类特性,推荐直接使用Vue实例的全局方法$on()/$emit(),或者使用插件Vuex来处理。
2.1.4 生命周期
Vue.js实例在创建时有一系列的初始化步骤,例如建立数据观察,编译模板,创建数据绑定等。在此过程中,我们可以通过一些定义好的生命周期钩子函数来运行业务逻辑。例如:
var vm = new Vue({ data: { a: 1 }, created: function () { console.log('created') } })
运行上述例子时,浏览器console中就会打印出created。
下图是实例的生命周期,可以根据提供的生命周期钩子说明Vue.js实例各个阶段的情况,Vue.js 2.0对不少钩子进行了修改,以下一并说明。
Vue.js实例生命周期(原图出自于Vue.js官网),如图2-2所示。
图2-2
init:在实例开始初始化时同步调用。此时数据观测、事件等都尚未初始化。2.0中更名为beforeCreate。
created:在实例创建之后调用。此时已完成数据绑定、事件方法,但尚未开始DOM编译,即未挂载到document中。
beforeCompile:在DOM编译前调用。2.0废弃了该方法,推荐使用created。
beforeMount: 2.0新增的生命周期钩子,在mounted之前运行。
compiled:在编译结束时调用。此时所有指令已生效,数据变化已能触发DOM更新,但不保证$el已插入文档。2.0中更名为mounted。
ready:在编译结束和$el第一次插入文档之后调用。2.0废弃了该方法,推荐使用mounted。这个变化其实已经改变了ready这个生命周期状态,相当于取消了在$el首次插入文档后的钩子函数。
attached:在vm.$el插入DOM时调用,ready会在第一次attached后调用。操作$el必须使用指令或实例方法(例如$appendTo()),直接操作vm.$el不会触发这个钩子。2.0废弃了该方法,推荐在其他钩子中自定义方法检查是否已挂载。
detached:同attached类似,该钩子在vm.$el从DOM删除时调用,而且必须是指令或实例方法。2.0中同样废弃了该方法。
beforeDestroy:在开始销毁实例时调用,此刻实例仍然有效。
destroyed:在实例被销毁之后调用。此时所有绑定和实例指令都已经解绑,子实例也被销毁。
beforeUpdate: 2.0新增的生命周期钩子,在实例挂载之后,再次更新实例(例如更新data)时会调用该方法,此时尚未更新DOM结构。
updated:2.0新增的生命周期钩子,在实例挂载之后,再次更新实例并更新完DOM结构后调用。
activated:2.0新增的生命周期钩子,需要配合动态组件keep-live属性使用。在动态组件初始化渲染的过程中调用该方法。
deactivated:2.0新增的生命周期钩子,需要配合动态组件keep-live属性使用。在动态组件移出的过程中调用该方法。
可以通过写一个简单的demo来更清楚地了解内部的运行机制,代码如下:
var vm = new Vue({ el : '#app', init: function() { console.log('init'); }, created: function() { console.log('created'); }, beforeCompile: function() { console.log('beforeCompile'); }, compiled: function() { console.log('compiled'); }, attached: function() { console.log('attached'); }, dettached: function() { console.log('dettached'); }, beforeDestroy: function() { console.log('beforeDestroy'); }, destroyed: function() { console.log('destroyed'); }, ready: function() { console.log('ready'); // 组件完成后调用$destory()函数,进行销毁 this.$destroy(); } });
输出结果为: