Vue2 - 生命周期

目录

  • 1,介绍
    • 1,初次渲染
    • 2,数据改变后的重渲染
  • 2,生成周期钩子执行顺序

1,介绍

创建 vue 实例和创建组件的流程基本一样。

1,初次渲染

  1. 做一些初始化操作,主要设置一些私有属性到实例中。

  2. 运行 beforeCreate

  3. 进入注入流程,处理属性,computedmethodsdataprovideinject等,最后使用代理模式将他们挂载到实例中(为了可通过 this 访问):

// 伪代码
function Vue(options) {
  var data = options.data();
  Vue.observe(data); // 变成响应式数据
  var methods = options.methods;
  // 代理模式挂载到实例上
  Object.defineProperty(this, "a", {
    get() {
      return data.a;
    },
    set(val) {
      data.a = val;
    },
  });

  Object.entries(methods).forEach(([methodName, fn]) => {
    this[methodName] = fn.bind(this);
  });
}

new Vue(vnode.componentOptions);
  1. 运行created

  2. 生成 render 函数,如果有配置render 函数则直接使用。没有则使用运行时编译器把模板 template 编译为 render。参考虚拟DOM的生成。

  3. 运行 beforeMount

  4. 生成真实 DOM

// 伪代码
function Vue(){
  // ... 其他代码
  var updateComponent = () => {
    this._update(this._render())
  }
  new Watcher(updateComponent);
  // ... 其他代码
}

创建一个 Watcher,传入一个函数updateComponent,该函数会运行render,把得到的vnode再传入_update函数执行。

在执行render函数的过程中,会收集所有依赖,之后依赖变化时会重新运行updateComponent函数

在执行_update函数的过程中,触发patch函数,由于目前没有旧树,因此直接为当前的虚拟dom树的每一个普通节点生成 elm 属性,即真实dom。

如果遇到组件的vnode,则会进入组件的实例化流程,该流程和创建vue实例流程基本相同,最终会把创建好的组件实例挂载到其 VNodecomponentInstance属性中,以便复用。

组件的 VNode 有2个属性:componentOptions对应传递的属性;componentInstance 对应组件实例。
对组件来说,diff 时会认为新旧2个的节点相同,所以组件实例会重用,并不会新建。如果属性变化了,给新节点的 componentOptions 重新赋值即可。

具体参考 数据响应式原理

  1. 运行 mounted

2,数据改变后的重渲染

  1. 数据变化后,所有依赖该数据的Watcher均会重新运行,这里仅考虑 updateComponent 函数对应的 Watcher

  2. Watcher会被调度器放到nextTick中运行,也就是微队列中,这样是为了避免多个依赖的数据同时改变后被多次执行

  3. 运行 beforeUpdate

  4. updateComponent函数重新执行

    在执行render函数的过程中,会去掉之前的依赖,重新收集所有依赖,将来依赖变化时会重新运行updateComponent函数

    在执行_update函数的过程中,触发patch函数,对新旧两棵树进行对比:

    • 普通html节点的对比会导致真实DOM被创建、删除、移动、更新

    • 组件节点的对比会导致组件被创建、删除、移动、更新

    当新组件需要创建时,进入实例化流程(前文中的初次渲染)。

    当旧组件需要删除时,会调用旧组件的$destroy方法删除组件,该方法会先触发beforeDestroy,然后递归调用子组件的$destroy方法,然后触发destroyed

    组件属性更新时,相当于组件的updateComponent函数被重新触发执行,进入重渲染流程,参考上面的步骤再来一遍。

  5. 运行updated

2,生成周期钩子执行顺序

示例:Vue 实例中渲染了 App.vueApp.vue 又引入了子组件A.vue

大致代码如下:

// main.js
import Vue from "vue";
import App from "./App.vue";

new Vue({
  render: (h) => h(App),
  beforeCreate() {
    console.log("vue实例 beforeCreate");
  },
  // ...
  // 其他生命周期函数
  // ...
  destroyed() {
    console.log("vue实例 destroyed");
  },
}).$mount("#app");

<template>
  <div id="app">
    <h1>Apph1>
    <A v-if="show" :count="count" />
    <button @click="count++">更新数据button>
    <button @click="show = !show">切换显示A组件button>
  div>
template>

<script>
import A from "./A.vue";

export default {
  components: { A },
  data() {
    return {
      show: true,
      count: 0,
    };
  },
  beforeCreate() {
    console.log("App beforeCreate");
  },
  // ...
  // 其他生命周期函数
  // ...
  destroyed() {
    console.log("App destroyed");
  },
};
script>

<template>
  <h1>A compnent: {{ count }}h1>
template>

<script>
export default {
  props: ["count"],
  beforeCreate() {
    console.log("A beforeCreate");
  },
  // ...
  // 其他生命周期函数
  // ...
  destroyed() {
    console.log("A destroyed");
  },
};
script>

初次渲染:

vue实例 beforeCreate
vue实例 created
vue实例 beforeMount

App beforeCreate
App created
App beforeMount

A beforeCreate
A created
A beforeMount
A mounted

App mounted
vue实例 mounted

数据改变后的重渲染:

注意,是因为修改的是响应式数据,所以会导致子组件触发更新,开始执行 beforeUpdate
所以,如果子组件没有使用父组件的响应式数据,则不会触发更新

App beforeUpdate

A beforeUpdate
A updated

App updated

当在 App.vue 通过 v-if 切换显示子组件 A.vue 时:

App beforeUpdate

A beforeDestroy
A destroyed

App updated

以上。

你可能感兴趣的:(vue2,前端,vue.js,生命周期)