Vue.js 前端开发 快速入门与专业应用
上QQ阅读APP看书,第一时间看更新

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();
          }
        });

输出结果为: