13

Vue 生命周期

下面是 index.html 的模板:

<div id="app"></div>

Vue 生命周期方法的调用顺序 / 生命周期方法在什么情况下被触发

我们在 new 一个 Vue 实例的过程中,生命周期的方法就会以一定的顺序相继执行,我们可以观察以下代码的执行结果。

注意:该 Vue 实例并未设置 el 属性。

new Vue({
    data: {
        message: 0
    },
    template: '<div>{{ message }}</div>',
    beforeCreate() {
        console.log(this.$el, 'beforeCreate');
    },
    created() {
        console.log(this.$el, 'created');
    },
    beforeMount() {
        console.log(this.$el, 'beforeMount');
    },
    mounted() {
        console.log(this.$el, 'mounted');
    },
    beforeUpdate() {
        console.log(this.$el, 'beforeUpdate');
    },
    updated() {
        console.log(this.$el, 'updated');
    },
    activated() {
        console.log(this.$el, 'activated');
    },
    deactivated() {
        console.log(this.$el, 'deactivated');
    },
    beforeDestroy() {
        console.log(this.$el, 'beforeDestroy');
    },
    destroyed() {
        console.log(this.$el, 'destroyed');
    },
    errorCaptured() {
        console.log(this.$el, 'errorCaptured');
    }
});

实例化 Vue 时的生命周期执行结果(未设置 el 属性)

我们可以看到 beforeCreate()created() 两个个生命周期方法被依次执行,而其他的生命周期方法并没有被触发执行。

如果我们加上 el 属性,或者调用 vm.$mount()方法。

new Vue({
    el: '#app', // 设置 el 属性
    // ...
});

或者

var app = new Vue({
    // ...
});

app.$mount('#root'); // 调用 Vue 实例的 $mount() 方法

实例化 Vue 时的生命周期执行结果

可以看到 beforeCreate()created()beforeMount()mounted() 四个生命周期方法被依次执行。

这里我们可以知道,在 new 一个 Vue 实例的时候,如果没有设置 el 属性或者调用 Vue 实例的 $mount() 方法,其实是只会执行 beforeCreate()created() 方法。原因在于,生命周期中的 mountd() 方法其实是把 Vue 实例中的 template属性里的 html 挂载到 el 属性对应的 dom 节点上,如果没有定义 el 属性或者没有调用 Vue 实例的 $mount() 方法,那么就无法执行挂载的动作,因为根本不知道要挂载到哪儿去。

beforeCreate()created()beforeMount()mounted() 四个方法都是一次性地,在 Vue 实例的生命周期中它们只会被触发调用一次。并且,beforeMount()mounted() 两个方法在服务端渲染的时候是不会被调用的,因为这两个方法是与 dom 的操作有一定关系,而服务端中没有 dom 执行的环境,所以也就不会有beforeMount()mounted() 了。

同时,可以看到 vm.$el 在各个生命周期中的值是不同的,在 beforeCreate()created() 中是 undefined,在 beforeMount() 中变成了 index.html 里的 <div id="app"></div>,等到执行 mounted() 方法时则又变成了渲染之后 <div>0</div>。在 mounted() 方法之后,我们再调用生命周期的方法拿到的 vm.$el 都是跟 mounted() 方法中一样的。

因此,我们是无法在 beforeCreate()created() 这两个生命周期中对 dom 做操作的,因为根本就拿不到 Vue 实例对应的那个 dom 节点,所以一般我们是会在 mounted() 中做一些与 dom 有关的操作。

剩下的生命周期方法为什么没有执行呢?接下来将介绍他们。

beforeUpdate() 和 updated()

这两个生命周期方法只有在数据更新的时候才会触发执行。

setTimeout(() => {
    app.message += 1;
}, 1000);

数据更新时生命周期执行结果

activated() 和 deactivated()

TODO...

beforeDestroy() 和 destroyed()

这两个生命周期方法会在 Vue 实例被销毁的时候触发执行。

setTimeout(() => {
    app.$destroy(); // 该方法会销毁 Vue 实例
}, 2000);

销毁 Vue 实例时生命周期执行结果

renderError()

该方法只有在开发时才会被调用,在正式打包上线时不会被调用,它的目的是帮我们更容易地调试一些 render() 中的一些错误。

new Vue({
    // ...
    render(h) {
        throw new TypeError('render error');
    },
    renderError(h, err) {
        return h('div', {}, err.stack);
    }    
});

renderError() 方法会在 render() 方法报错的时候被触发执行,页面渲染出来的内容就是错误信息。该方法只有在当前 Vue 实例的 render() 方法中出现错误的时候才会被触发,子组件的报错是不会被当前 Vue 实例捕获到的。

errorCaptured()

该方法可以用到正式环境中,帮助我们收集一些线上的错误,并且其可以捕获到子组件的报错。

new Vue({
    // ...
    errorCaptured(h, err) {
        // ...
    }    
});

生命周期图示解析

下面是 Vue 的生命周期的图示,接下来将对其进行简单的解析。

生命周期图示

new 一个 Vue 实例的过程中,首先执行了 init() 方法,在 init() 方法的 Events & LifeCycle 之后 执行 beforeCreate() 方法,在 init() 方法的 injections & reactivity 之后执行 created 方法。可以看到,在执行 beforeCreate() 的时候,Vue 实例的事件绑定是已经完成的了,但是它的响应式还没有,所以我们不在 beforeCreate() 中去修改 data 里面的数据,如果你要做一些 ajax 请求,然后给 Vue 实例的 data 赋新值,最早也要放在 created() 中来做。

接下来是会判断是否设置了 el 属性,如果有,则会去判断是否有 template 属性,如果没有,则会等到调用了 vm.$mount() 方法之后再去判断是否有 template 属性。

如果设置了 template 属性,会将 template 解析为一个 render() 方法,如果没有设置,会将 el 属性对应的 html 解析为 template。这一步可以不设置 template 属性,通过手动调用 render() 方法来实现。

new Vue({
    // ...
    render(h) {
        return h('div', {}, this.message);
    }    
});

render() 方法接收一个入参,该入参是一个创建虚拟节点的方法,也就是说,render() 方法会返回一个虚拟节点。render() 方法会在 beforeMount()mounted() 之间执行,创建 vm.$el 属性并且替换掉 el 属性对应的 html 节点,这就解释了前面的内容,在 beforeMount()vm.$elindex.html 中的 <div id="app"></div>,在 mounted()vm.$el 变成了渲染 template 后的 <div>0</div>,这中间就是调用了 render() 方法。用这种方法来做,与设置 template 属性的结果是一样的。

在用 .vue 文件开发的过程中,是没有在 Vue 实例中设置 template 属性的,而是在 .vue 文件中编写了 <template></template> 标签,再经过 vue-loader 处理,直接解析为 render() 方法,这个方式有一个好处。因为解析 Vue 实例中的 template 属性为 render() 方法是一个比较耗时的过程,使用 vue-loader 来帮我们处理,会使得在页面上执行 vue 代码的效率更高。

mounted() 执行之后,这个 Vue 实例其实就已经初始化完成了。后续的生命周期都是需要一些外部的触发才会执行的,比如,有数据更新时会调用 beforeUpdate()updated(),Vue 实例被销毁时会调用 beforeDestroy()destroyed()

这就是一个 Vue 实例从新建到销毁的整个流程。

以上是我对 Vue 生命周期的一点见解,若有认为不正确或不妥当的地方,欢迎指正!


3santiago3
113 声望2 粉丝