jd-smart-fe / shared

共享文档
MIT License
25 stars 4 forks source link

Vue 生命周期钩子函数 #12

Open yanglikun1 opened 6 years ago

yanglikun1 commented 6 years ago

前言

生命周期通俗点说是一个对象的生老病死(Cradle-to-Grave)但对于Vue而言,Vue的生命周期也就是 Vue实例及组件实例从创建到卸载的过程。

Vue的生命周期

Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是Vue的生命周期。通俗说就是Vue实例从创建到销毁的过程。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

以下根据vue官网生命周期图示展开讲解 相关代码及实例均可clone: https://github.com/yanglikun1/bascVue.git 下载。

Vue生命周期钩子函数

beforeCreate

beforeCreate:在实例初始化之后,数据观测和event/watcher事件配置之前调用。

如何理解此钩子,堆上代码最直接: [代码1-1]

beforeCreate() {
    window.console.log(this);
    window.console.log(`el:    ${this.$el}`);  
    window.console.log(`data   : ${this.$data}`);
  },

根据执行结果提示 beforeCreate 的解释是“创建实例之前执行的钩子事件”:对此处的理解是 数据观测、event/watch的配置是实例的一部分,beforeCreate只是new 了实例。

created

created:在实例创建完成后被立即调用。在这一步,实例已完成数据观测、属性和方法的运算,watch/event事件回调。然而,挂载阶段还没开始,$el属性目前不可见。

[代码2-1]

 created() {
    window.console.log(`el     : ${this.$el}`);
    window.console.log(`data   : ${JSON.stringify(this.$data)}`); 
    window.console.log(`msg: ${this.msg}`);
  },

Vue会自动将data里面的数据进行递归抓换成getter和setter进行观测;

对比 beforeCreate下this实例和created 中this实例的区别。

el选项的有无对生命周期过程的影响

首先系统会判断对象中有没有el选项,有el选项,则继续编译过程;没有el选项,则停止编译,也意味着暂时停止了生命周期,直到 vm.$mount(el),页面此时必需已有el存在否则报错。

  1. 注释new Vue() 中el选项查看浏览器consle; [代码3-1]
    new Vue({
    // el: '#first',
    data() {
    return {
      msg: 30,
    };
    },
    // template: '<App/>',
    beforeCreate() {
    window.console.log('调用了beforCreate函数===============');
    },
    created() {
    window.console.log('调用了created函数===================');
    },
    beforeMount() {
    window.console.log('调用了beforeMount函数=============');
    },
    mounted() {
    window.console.log('调用了mounted函数===================');
    },

    正如说明中所说停止编译,也意味着暂时停止了生命周期。

2.在created钩子中 添加 this.$mount('#first') 查看浏览器consle; [代码3-2]

 new Vue({
 // el: '#first',
  data() {
    return {
      msg: 30,
    };
  },
  // template: '<App/>',
  beforeCreate() {
    window.console.log('调用了beforCreate函数===============');
  },
  created() {
    window.console.log('调用了created函数===================');
    this.$mount('#first');
  },
  beforeMount() {
    window.console.log('调用了beforeMount函数=============');
  },
  mounted() {
    window.console.log('调用了mounted函数===================');
  },

使用this.$mount 指定挂载dom 生命周期继续。

3.将this.$mount('#app') 放在最前和最后查看区别。

[代码3-3]

 new Vue({
 // el: '#first',
  data() {
    return {
      msg: 30,
    };
  },
  // template: '<App/>',
  beforeCreate() {
    window.console.log('调用了beforCreate函数===============');
  },
  created() {
    this.$mount('#first');
    window.console.log('调用了created函数===================');
  },
  beforeMount() {
    window.console.log('调用了beforeMount函数=============');
  },
  mounted() {
    window.console.log('调用了mounted函数===================');
  },

如果在beforeCreate中使用this.$mount() ,会出现什么情况?

template参数选项的有无对生命周期的影响

  1. 如果Vue实例对象中有template参数选项,则将其作为模版编译浏览器可读的HTML;
  2. 如果没有template参数选项,则外部的html作为模版编译,也就是说template参数的优先级要比html高。

修改外部html中挂载元素id为first,修改new Vue()中el选项值为#first 并注释掉template参数,在beforeMount/mounted中打印this.$el查看。 [代码4-1]

new Vue({
  el: '#first',
  data() {
    return {
      msg: 30,
      age: 1,
      number: 0,
    };
  },
  router,
  // template: '<App/>',
  beforeCreate() {
    window.console.log('调用了beforCreate函数===============');
  },
  created() {
    window.console.log('调用了created函数===================');
  },
  beforeMount() {
    window.console.log('调用了beforeMount函数=============');
    window.console.log(this.$el);
  },
  mounted() {
    window.console.log('调用了mounted函数===================');
    window.console.log(this.$el);
  },

我们可以看到在beforMount中数据尚未渲染到dom;

为什么说template比外部模版优先级高呢?咱们继续往下

打开注释掉template参数,在beforeMount/mounted中打印this.$el查看。 [代码4-2]

new Vue({
  el: '#first',
  data() {
    return {
      msg: 30,
      age: 1,
      number: 0,
    };
  },
  router,
  template: '<App/>',
  beforeCreate() {
    window.console.log('调用了beforCreate函数===============');
  },
  created() {
    window.console.log('调用了created函数===================');
  },
  beforeMount() {
    window.console.log('调用了beforeMount函数=============');
    window.console.log(this.$el);
  },
  mounted() {
    window.console.log('调用了mounted函数===================');
    window.console.log(this.$el);
  },

可以发现mounted 中的this.$el 被替换成了app, new Vue() 实例的template的优先级高于外部html 。

beforeMount

beforeMount:在挂载开始之前被调用,相关的render函数首次被调用。 上例中可以看到 在此时模版已经被编译但尚未渲染;

思考:此时template模版中的组件的状态

[代码5-1]

  beforeCreate() {
    window.console.log('App.vue调用了beforCreate函数');
  },
  created() {
    window.console.log('App.vue调用了created函数');
  },
  beforeMount() {
    window.console.log('App.vue调用了beforeMount函数');
  },
  mounted() {
    window.console.log('App.vue调用了mount函数');
  },
  beforeUpdate() {
    window.console.log('App.vue调用了beforeUpdate函数');
  },
  updated() {
    window.console.log('App.vue调用了updated函数');
  },

此时template模版中的组件实例已经挂载到相应的dom上,并且在Vue 实例完成挂载之前。

mounted

mounted:el 被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素,当 mounted 被调用时vm.$el也在文档内。

注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted。

beforeUpdate

beforeUpdate:数据更新时调用,发生在虚拟dom重新渲染和打补丁之前。

(只有Vue实例中的数据被“写入”到我们的模板中)你可以在这个钩子中进一步的更改状态,这不会触发附加的重渲染过程。

[代码6-1]

  <body>
    <div id="first" style="font-size:50px;">{{msg}} {{age}}
      <button @click="msg = 2">修改msg</button>
      <button @click="number = 2">修改number</button>
    </div>
    <!-- built files will be auto injected -->
  </body>
  new Vue({
  el: '#first',
  data() {
    return {
      msg: 30,
      age: 1,
      number: 0,
    };
  },
  beforeUpdate() {
    window.console.log('调用了beforeUpdate函数=============');
  },
  updated() {
    window.console.log('调用了updated函数==================');
  }

点击"修改number"按钮结果是:没有触发beforUpdate/updated。

updated

updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件DOM已经更新,所以你现在可以执行依赖于 DOM 的操作。

然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。注意 updated 不会承诺所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以用 vm.$nextTick 替换掉 updated;

测试发现数据的改变引起的只是本实例的beforeUpdate/updated的触发。

activated

activated:keep-alive 组件激活时调用,组件第一次执行时激活keep-alive。

[代码7-1]

    <div id="app">
   <div class="demo">
     <keep-alive>
        <component :is="view"></component>
     </keep-alive>
    <button @click="view = 'HelloWorld'">点击切换</button>
   </div>

template 选项的模版组件,初始化触发了 TestKeep子组件的activate 钩子函数。

deactivated

deactivated:keep-alive 组件停用时调用。

当组件在 <keep-alive> 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。

按照[代码7-1] 点击 “点击切换” 按钮

我们看到子组件 TeatKeep 调用了deactivated,HelloWorld组件的activate触发。

beforeDestroy

beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。 [代码8-1]

<div id="app">
   <div class="demo">
      <p><b>beforeDestroy</b>:实例销毁之前调用。在这一步,实例仍然完全可用。</p> <button @click="destoryd" >销毁App</button>
     <p><b>age = {{age}}</b></p>
   </div>
</div>
<!-- destoryd() 函数 执行 this.$destory-->
  beforeCreate() {
    window.console.log('App.vue调用了beforCreate函数');
  },
  created() {
    window.console.log('App.vue调用了create函数');
  },
  beforeMount() {
    window.console.log('App.vue调用了beforeMount函数');
  },
  mounted() {
    window.console.log('App.vue调用了mount函数');
  },
  beforeUpdate() {
    window.console.log('App.vue调用了beforeUpdate函数');
  },
  updated() {
    window.console.log('App.vue调用了Update函数');
  },
  beforeDestroy() {
    window.console.log('App.vue调用了beforeDestroy函数');
    this.age = 50;
    window.console.log(this.age);
    // 此钩子里面修改age 数据。
  },
  destroyed() {
    window.console.log('App.vue调用了destroyed函数');
  },

此时可以获取实例,仍触发updated函数,但不会触发beforUpdate函数,页面不再被渲染。

destroyed

destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 [代码9-1]

    <div id="app">
   <!-- <div class="fileimge"></div> -->
   <div class="demo">
      <p><b>beforeDestroy</b>:实例销毁之前调用。在这一步,实例仍然完全可用。</p> <button @click="destoryd" >销毁App</button>
      <p><b>destroyed</b>:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。</p>
     <p><b>age = {{age}}</b></p>
     <component :is="view"></component>
     <!--TestKeep子组件-->
   </div>
  </div>

点击‘销毁App’ 的结果

此时可以获取实例,但不能触发updated函数,子实例被销毁,修改v-show不会触发渲染。仍可获取this 但是关于实例的监听及子实例均已销毁,触发了子组件的销毁钩子,在点击'销毁App'按钮 已经没有效果。

errorCaptured

errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

本身报错此钩子不会触发。 [代码10-1]

// 子组件抛出 错误
errorCaptured(err, vm, info) {
    window.console.log('error================');
    const error = `${err.stack}\n\nfound in ${info} of component`;
    window.console.log(error);
    return false; // 阻止向上传播;
  }

总结

讲完这些Vue生命周期钩子我们还会有很多疑惑,不过通过我们的不断探索会发现更多的使用场景和方式。你我共勉!!!