Vue 實例的生命週期

Vue 中,每一個實例都有本身的一個完整的生命週期,包括開始建立 —— 初始化數據 —— 編譯模板 —— 將實例掛載到 DOM —— 渲染 —— 更新 —— 渲染 —— 卸載等一系列過程。同時在這個過程當中也會運行一些叫作生命週期鉤子的函數,這樣咱們就能夠在實例生命週期的不一樣階段作一些須要作的事情。javascript

1、vue 實例和組件的區別

這裏可能會有個小疑惑,怎麼忽然又說是 Vue 實例呢?實例和組件有什麼區別嗎?css

SPA(Single Page Application) 應用中,咱們只會建立一個 Vue 根實例,整個應用都是經過這個根實例啓動的。在經過 vue-cli 腳手架生成的項目中, main.js 裏建立了 Vue 根實例:
<!-- more -->html

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>',
});

經過 new Vue() 建立一個 Vue 實例。在實例化 Vue 時,須要傳入一個選項對象,它能夠包含掛載元素 el、路由router、模板 template、數據 data、方法 methods、生命週期鉤子等選項。vue

Vue 組件是被擴展的 Vue 實例。Vue 實例相似,它也須要傳入一個選項對象,包含數據、模板、生命週期鉤子函數等等。在前面咱們只介紹了單文件組件的方式來建立組件,也能夠這樣來建立一個組件:java

// 定義一個名爲 button-counter 的新組件
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

能夠發現,和建立 Vue 實例很是的相似,全部的 Vue 組件同時都是 Vue 的實例。因此咱們說:實例的生命週期也叫作組件的生命週期。vue-cli

2、Vue 實例的生命週期

Vue 實例從建立到銷燬的過程,就是生命週期。文章的開頭咱們提到了生命週期鉤子函數,它是框架提供的函數,可以讓開發人員的代碼參與到組件的生命週期中。也就是說,經過鉤子函數,能夠控制組件的行爲。這裏有兩個點須要記住:緩存

  • Vue 實例的的生命週期中,會自動調用這些生命週期函數,咱們只須要提供這些鉤子函數便可;
  • 生命週期鉤子函數的名字是固定的,不可更改,不能夠自定義或者添加鉤子函數。

生命週期圖示:app

lifecycle

圖中,紅色矩形框表明着在生命週期對應階段的鉤子函數。框架

3、生命週期函數

咱們經過三種案例來學習生命週期函數:函數

  • 單組件的生命週期
  • 父子組件的生命週期
  • 兄弟組件的生命週期

案例中,經過打開控制檯查看鉤子函數打印的內容來學習。

一、單組件的生命週期

<template>
  <div class='container'>
    <h3>單組件的生命週期(打開控制檯查看)</h3>
    <button @click='dataVar += 1'>更新 {{dataVar}}</button>
    <button @click='handleDestroy'>銷燬</button>
  </div>
</template>

<script>
const compName = 'single';

export default {
  name: 'SingleComLifecycle',
  data() {
    return {
      dataVar: 1,
    };
  },
  beforeCreate() {
    console.log(`--${compName}--beforeCreate`); // eslint-disable-line
  },
  created() {
    console.log(`--${compName}--created`); // eslint-disable-line
  },
  beforeMount() {
    console.log(`--${compName}--beforeMount`); // eslint-disable-line
  },
  mounted() {
    console.log(`--${compName}--mounted`); // eslint-disable-line
  },
  beforeUpdate() {
    console.log(`--${compName}--beforeUpdate`); // eslint-disable-line
  },
  updated() {
    console.log(`--${compName}--updated`); // eslint-disable-line
  },
  beforeDestroy() {
    console.log(`--${compName}--beforeDestroy`); // eslint-disable-line
  },
  destroyed() {
    console.log(`--${compName}--destroyed`); // eslint-disable-line
  },
  methods: {
    handleDestroy() {
      this.$destroy();
    },
  },
};
</script>


<style lang="postcss" scoped>
.container {
  width: 70%;
  margin: 0 auto;
  background-color: aliceblue;
  padding: 50px;
  text-align: center;
}
button {
  padding: 6px;
  background-color: #35b880;
  border: none;
  color: white;
  font-size: 16px;
  margin: 5px;
}
</style>

初始化組件時打印:

--single--beforeCreate
--single--created
--single--beforeMount
--single--mounted

data 中的數據變化時打印:

--single--beforeUpdate
--single--updated

組件銷燬時打印:

--single--beforeDestroy
--single--destroyed

因此咱們能夠得出如下結論:

  • 初始化組件時,僅執行了 beforeCreate/Created/beforeMount/mounted 四個鉤子函數
  • 當改變 data 中定義的變量(響應式變量)時,會執行 beforeUpdate/updated 鉤子函數
  • 當切換組件(當前組件未緩存)時,會執行 beforeDestory/destroyed 鉤子函數
  • 初始化和銷燬時的生命鉤子函數均只會執行一次,beforeUpdate/updated 可屢次執行

二、父子組件的生命週期

建立一個父組件:

<template>
  <div class='container'>
    <h3>父子組件的生命週期(打開控制檯查看)</h3>
    <child-com :compName='dataVar.toString()'></child-com>
    <button @click='dataVar += 1'>父組件更新 {{dataVar}}</button>
    <button @click='handleDestroy'>父組件銷燬</button>
  </div>
</template>

<script>
import ChildCom from './ChildCom';

const COMPONENT_NAME = 'parent';
export default {
  data() {
    return {
      dataVar: 1,
    };
  },
  components: {
    'child-com': ChildCom,
  },
  beforeCreate() {
    console.log(`--${COMPONENT_NAME}--beforeCreate`); // eslint-disable-line
  },
  created() {
    console.log(`--${COMPONENT_NAME}--created`); // eslint-disable-line
  },
  beforeMount() {
    console.log(`--${COMPONENT_NAME}--beforeMount`); // eslint-disable-line
  },
  mounted() {
    console.log(`--${COMPONENT_NAME}--mounted`); // eslint-disable-line
  },
  beforeUpdate() {
    console.log(`--${COMPONENT_NAME}--beforeUpdate`); // eslint-disable-line
  },
  updated() {
    console.log(`--${COMPONENT_NAME}--updated`); // eslint-disable-line
  },
  beforeDestroy() {
    console.log(`--${COMPONENT_NAME}--beforeDestroy`); // eslint-disable-line
  },
  destroyed() {
    console.log(`--${COMPONENT_NAME}--destroyed`); // eslint-disable-line
  },
  methods: {
    handleDestroy() {
      this.$destroy();
    },
  },
};
</script>


<style lang="postcss" scoped>
.container {
  width: 70%;
  margin: 20px auto;
  background-color: aliceblue;
  padding: 50px;
  text-align: center;
}
button {
  padding: 6px;
  background-color: #2196f3;
  border: none;
  color: white;
  font-size: 16px;
  margin: 5px;
}
</style>

建立一個子組件:

<template>
  <div>
    <div>父組件傳遞的props:{{compName}}</div>
    <button @click='dataVar += 1'>子組件更新 {{dataVar}}</button>
    <button @click='handleDestroy'>子組件銷燬</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dataVar: 1,
    };
  },
  props: {
    compName: {
      type: String,
      default: 'single',
    },
  },
  beforeCreate() {
    console.log(` --此時data未初始化--child--beforeCreate`); // eslint-disable-line
  },
  created() {
    console.log(`--${this.compName}--child--created`); // eslint-disable-line
  },
  beforeMount() {
    console.log(`--${this.compName}--child--beforeMount`); // eslint-disable-line
  },
  mounted() {
    console.log(`--${this.compName}--child--mounted`); // eslint-disable-line
  },
  beforeUpdate() {
    console.log(`--${this.compName}--child--beforeUpdate`); // eslint-disable-line
  },
  updated() {
    console.log(`--${this.compName}--child--updated`); // eslint-disable-line
  },
  beforeDestroy() {
    console.log(`--${this.compName}--child--beforeDestroy`); // eslint-disable-line
  },
  destroyed() {
    console.log(`--${this.compName}--child--destroyed`); // eslint-disable-line
  },
  methods: {
    handleDestroy() {
      this.$destroy();
    },
  },
};
</script>

<style lang="postcss" scoped>
button {
  padding: 6px;
  background-color: #35b880;
  border: none;
  color: white;
  font-size: 16px;
  margin: 5px;
}
</style>

初始化組件時打印:

--parent--beforeCreate
--parent--created
--parent--beforeMount
--此時data未初始化--child--beforeCreate
--1--child--created
--1--child--beforeMount
--1--child--mounted
--parent--mounted

當子組件 data 中的值變化時打印:

--1--child--beforeUpdate
--1--child--updated

當父組件 data 中的值變化時打印(子組件接受了父組件傳遞的 props):

--parent--beforeUpdate
--2--child--beforeUpdate
--2--child--updated
--parent--updated

當子組件銷燬時打印:

--1--child--beforeDestroy
--1--child--destroyed

當父組件銷燬時打印:

--parent--beforeDestroy
--1--child--beforeDestroy
--1--child--destroyed
--parent--destroyed

結論:

  • 僅當子組件完成掛載後,父組件纔會掛載
  • 父子組件在 data 變化中是分別監控的,可是在更新 props 中的數據是關聯的
  • 銷燬父組件時,先將子組件銷燬後纔會銷燬父組件

三、兄弟組件的生命週期

建立一個單組件:

<template>
  <div class='container'>
    <button @click='dataVar += 1'>更新 {{dataVar}}</button>
    <button @click='handleDestroy'>銷燬</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dataVar: 1,
    };
  },
  props: {
    compName: {
      type: String,
      default: 'single',
    },
  },
  beforeCreate() {
    console.log(`--此時data未初始化--beforeCreate`); // eslint-disable-line
  },
  created() {
    console.log(`--${this.compName}--created`); // eslint-disable-line
  },
  beforeMount() {
    console.log(`--${this.compName}--beforeMount`); // eslint-disable-line
  },
  mounted() {
    console.log(`--${this.compName}--mounted`); // eslint-disable-line
  },
  beforeUpdate() {
    console.log(`--${this.compName}--beforeUpdate`); // eslint-disable-line
  },
  updated() {
    console.log(`--${this.compName}--updated`); // eslint-disable-line
  },
  beforeDestroy() {
    console.log(`--${this.compName}--beforeDestroy`); // eslint-disable-line
  },
  destroyed() {
    console.log(`--${this.compName}--destroyed`); // eslint-disable-line
  },
  methods: {
    handleDestroy() {
      this.$destroy();
    },
  },
};
</script>


<style lang="postcss" scoped>
.container {
  width: 70%;
  margin: 0 auto;
  background-color: aliceblue;
  padding: 10px;
  text-align: center;
}
button {
  padding: 6px;
  background-color: #35b880;
  border: none;
  color: white;
  font-size: 16px;
  margin: 5px;
}
</style>

引入兩個單組件,構建兄弟組件:

<template>
  <div class='container'>
    <h3>兄弟組件的生命週期(打開控制檯查看)</h3>
    <div>兄弟1
      <single compName='cihld1'></single>
    </div>
    <div>兄弟2
      <single compName='child2'></single>
    </div>
    <div>
      <p>兄弟兩個的父親</p>
      <button @click='dataVar += 1'>更新 {{dataVar}}</button>
      <button @click='handleDestroy'>銷燬</button>
    </div>
  </div>
</template>

<script>
import Single from './Single';

const COMPONENT_NAME = 'parent';
export default {
  data() {
    return {
      dataVar: 1,
    };
  },
  components: {
    single: Single,
  },
  beforeCreate() {
    console.log(`--${COMPONENT_NAME}--beforeCreate`); // eslint-disable-line
  },
  created() {
    console.log(`--${COMPONENT_NAME}--created`); // eslint-disable-line
  },
  beforeMount() {
    console.log(`--${COMPONENT_NAME}--beforeMount`); // eslint-disable-line
  },
  mounted() {
    console.log(`--${COMPONENT_NAME}--mounted`); // eslint-disable-line
  },
  beforeUpdate() {
    console.log(`--${COMPONENT_NAME}--beforeUpdate`); // eslint-disable-line
  },
  updated() {
    console.log(`--${COMPONENT_NAME}--updated`); // eslint-disable-line
  },
  beforeDestroy() {
    console.log(`--${COMPONENT_NAME}--beforeDestroy`); // eslint-disable-line
  },
  destroyed() {
    console.log(`--${COMPONENT_NAME}--destroyed`); // eslint-disable-line
  },
  methods: {
    handleDestroy() {
      this.$destroy();
    },
  },
};
</script>

<style lang="postcss" scoped>
.container {
  width: 70%;
  margin: 5px auto;
  background-color: aliceblue;
  padding: 50px;
  text-align: center;
}
button {
  padding: 6px;
  background-color: #2196f3;
  border: none;
  color: white;
  font-size: 16px;
  margin: 5px;
}
</style>

初始化組件時打印:

--parent--beforeCreate
--parent--created
--parent--beforeMount
--此時data未初始化--beforeCreate
--cihld1--created
--cihld1--beforeMount
--此時data未初始化--beforeCreate
--child2--created
--child2--beforeMount
--cihld1--mounted
--child2--mounted
--parent--mounted

child1 更新和銷燬時,打印:

--cihld1--beforeUpdate
--cihld1--updated
--cihld1--beforeDestroy
--cihld1--destroyed

child2 更新和銷燬時,打印:

--cihld2--beforeUpdate
--cihld2--updated
--cihld2--beforeDestroy
--cihld2--destroyed

當父組件銷燬時,打印:

--parent--beforeDestroy
--cihld1--beforeDestroy
--cihld1--destroyed
--child2--beforeDestroy
--child2--destroyed
--parent--destroyed

結論:

  • 組件的初始化(mounted以前)分開進行,掛載是從上到下依次進行
  • 當沒有數據關聯時,兄弟組件之間的更新和銷燬是互不關聯的
歡迎關注個人博客: https://togoblog.cn/