Vue指南的要点笔记(一)

本篇开始,仔细地阅读Vue官方指南,并记录学习过程中思考、实践、总结的要点。 本篇包含的主要是内容是:

  1. 生命周期图示中要点
  2. 2.6.0新增的动态参数属性

生命周期图示中要点

这是官方的生命周期图示:

根据这张图示,可以总结出的要点有:

  1. 如果没有eloption,new Vue构造出的实例不会立即渲染到dom上,除非再次调用实例的$mount方法,将vue实例与dom元素进行挂载:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let vue = new Vue({
    el: '#app'
    //,...
    });

    //等价于
    let vue = new Vue({
    //...
    });

    vue.$mount('#app');

    而且没有eloption的实例,它的生命周期只会进行到created这个钩子,当它被$mount到一个dom元素之后,created后面的生命周期才能继续。

  2. vm.$data这个属性、vm的计算属性、vm的watch属性、以及vm.$watch方法,最早要在created这个钩子里面才能生效。只有生命周期进行到created这个钩子的时候,vue实例才完成了injections的初始化;
  3. vm.$el这个属性,最早要在beforeMount这个钩子里面才能访问到;
  4. vm实例methods内定义的行为方法,最早要在created这个钩子里面才能访问到;
  5. vm.$refs这个属性内的节点引用,最早要在mounted这个钩子里面才能访问到;
  6. 当设置了eloption,且未设置templateoption的时候,会把elouterHTML作为渲染模板进行编译,由于是outerHTML,所以el元素本身也能使用vue的模板语法,比如动态地设置class属性;
    以上几点都可以通过下面的示例进行说明:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    <div id="vue" :class="{enabled: enabled}">
    <p ref="content">{{msg}}</p>
    </div>

    <script type="text/javascript">
    let obj = {
    msg: new Date(),
    enabled: true
    };
    let vue = new Vue({
    data: obj,
    beforeCreate() {
    console.log('beforeCreate', this.$el, this.$data, this.$data === obj );
    //beforeCreate undefined undefined false

    console.log(this.updateMsg);
    //undefined

    console.log(this.$refs.content);
    //undefined
    },
    created() {
    console.log('created', this.$el, this.$data, this.$data === obj);
    //created undefined {__ob__: Observer} true

    console.log(this.updateMsg);
    //function updateMsg

    console.log(this.$refs.content);
    //undefined
    },
    beforeMount(){
    console.log('beforeMount', this.$el);
    //beforeMount div#vue

    console.log(this.$refs.content);
    //undefined
    },
    mounted(){
    console.log('mounted', this.$el);
    //mounted div#vue

    console.log(this.$refs.content);
    //<p></p>
    },
    methods: {
    updateMsg(){
    this.msg = new Date();
    }
    }
    });

    vue.$mount('#vue');

    </script>
  7. 不管有没有设置eloption,只要设置了templateoption,就会把template直接用于底层的render函数,并根据template创建出新的dom元素,替换掉eloption所指向的dom元素,并赋值给vm.$el;所以更加安全地访问vm.$el的时机点,最早是在mounted这个钩子函数里面。

  8. templateoption的应该只能包含一个顶层元素,这个顶层元素最后会被创建到dom里面,成为最终的vm.$el;由此可知,所谓的单文件的vue组件,实际上就是把template部分定义的内容,设置为了组件的templateoption而已。
  9. 8个生命周期函数,除了通过vm实例的option来写,也可以通过以下方式,手工添加:

    1
    2
    3
    4
    5
    6
    7
    8
    this.$on('hook:beforeDestroy',()=>{});
    this.$on('hook:destroyed',()=>{});
    this.$on('hook:beforeCreate',()=>{});
    this.$on('hook:created',()=>{});
    this.$on('hook:beforeMount',()=>{});
    this.$on('hook:mounted',()=>{});
    this.$on('hook:beforeUpdate',()=>{});
    this.$on('hook:updated',()=>{});

    这个方式在需要编写一些更加通用性的功能的时候,比如plugins,会起到很大的作用。同时由于vm实例的生命周期,第一步就是init events and lifecyle,所以上面的这些hook事件,最早在beforeCreate这个option里面就能使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <div id="vue" :class="{enabled: enabled}">
    <p ref="content">{{msg}}</p>
    </div>

    <script type="text/javascript">
    let obj = {
    msg: new Date(),
    enabled: true
    };
    let vue = new Vue({
    data: obj,
    beforeCreate() {
    this.$on('hook:created', ()=>{
    console.log('another created hook');
    //another created hook
    })
    },
    created() {
    console.log('created');
    //created
    }
    });

    vue.$mount('#vue');

    </script>
  10. vm的侦听属性方法,要比手工调用vm.$watch添加的回调先执行; 计算属性晚于beforeUpdate这个钩子函数之后执行,但是会在updated这个钩子函数之前执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    <div id="vue" :class="{enabled: enabled}">
    <p ref="content">{{reverseMsg}}</p>
    </div>

    <script type="text/javascript">
    let obj = {
    msg: new Date() + "",
    enabled: true
    };
    let vue = new Vue({
    data: obj,
    watch: {
    msg(){
    console.log('msg changed in watch property');
    }
    },
    computed: {
    reverseMsg(){
    let r = this.msg.split('').reverse().join('');
    console.log('reverse msg changed');
    return r;
    }
    },
    created() {
    this.$watch('msg', ()=>{
    console.log('msg changed from created');
    });
    },
    mounted(){
    this.msg = new Date() + "1";
    },
    beforeUpdate(){
    console.log('beforeUpdate');
    },
    updated(){
    console.log('updated');
    }
    });

    vue.$mount('#vue');

    //reverse msg changed
    //msg changed in watch property
    //msg changed from created
    //beforeUpdate
    //reverse msg changed
    //updated
    </script>

2.6.0新增的动态参数属性

2.6.0开始,模板里面bind元素的attributes,可以使用动态参数了,不过这个动态参数跟通常取值的模板语法不太一样。先来看它正常的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="vue" v-bind:[titlename]="titleValue">
</div>

<script type="text/javascript">
let vue = new Vue({
data: {
titlename: 'title',
titleValue: 'this is a test'
}
});

vue.$mount('#vue');
</script>

最后dom里面会输出:

1
<div id="vue" title="this is a test"></div>

bind动态参数的用法,与bind常规参数的区别就是中括号,v-bind:[titlename],表示最终要bind的属性名称,由vm实例的titlename属性值来决定。动态参数的值如果是一个非空字符串,就以该字符串作为attribute的名称,如果是null值,表示移除该attribute。

直接在html中书写vm实例的模板(eloption或通过vm.$mount与html中的元素挂载),要注意:当html被浏览器加载完,vm实例还没构建前,html中与vm实例对应的元素,如果使用了动态参数绑定attributes,动态参数名称会全部转为小写,导致一些意外的情况。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="vue" v-bind:[titleName]="titleValue">
</div>

<script type="text/javascript">
let vue = new Vue({
data: {
titleName: 'title',
titleValue: 'this is a test'
}
});

vue.$mount('#vue');
</script>

上面这个代码在浏览器运行会报错Property or method "titlename" is not defined on the instance but referenced during render。因为它是直接在html中运行的,div#vue加载到dom以后,被实例化为vm实例前,这个元素上所有的属性名称都会被转为小写:v-bind:[titleName]=>v-bind:[titleName],导致最后参与vm实例化的时候,这个元素的outerHTML实际上是:

1
<div id="vue" v-bind:[titlename]="titleValue"></div>

而不是

1
<div id="vue" v-bind:[titleName]="titleValue"></div>

最后vm实例就把<div id="vue" v-bind:[titlename]="titleValue"></div>编译为了渲染模板,当实际渲染的时候,[titlename]就变为了取vm实例titlename属性的值,而vm实例的data里面只定义了titleName,没有titlename,所以导致报错。

如果直接通过templateoption指定模板,就不会有这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="vue">
</div>

<script type="text/javascript">
let vue = new Vue({
data: {
titleName: 'title',
titleValue: 'this is a test'
},
template: `<div v-bind:[titleName]="titleValue"></div>`
});

vue.$mount('#vue');
</script>

所以单文件的Vue组件也不会有这个问题,因为它本质上利用template来构造vm实例的。

同理,下面的写法如果直接放在html里面也是有问题的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="vue" v-bind:['title' + Name]="titleValue">
</div>

<script type="text/javascript">
let vue = new Vue({
data: {
titleName: 'title',
name: 'Name',
titleValue: 'this is a test'
},
});

vue.$mount('#vue');
</script>

div#vuev-bind:['title' + Name]这个名称,违背了html5文档的规范:属性名不能出现空格、引号。 这种改用template写法也是不行的,Vue严格规定了动态参数不能使用一些特殊字符,否则会出现下面的警告:

1
Invalid dynamic argument expression: attribute names cannot contain spaces, quotes, <, >, / or =.

动态参数不仅只有v-bind指令可以用,其它指令都能用;v-on指令使用动态参数时,修饰符不能用动态参数