前端 | Vue 3生命周期完整指南
Vue2 和 Vue3 中的生命周期鉤子的工作方式非常相似,我們?nèi)匀豢梢栽L問相同的鉤子,也希望將它們能用于相同的場景。
如果項目使用 選項 API,就不必更改任何代碼了,因為 Vue3 兼容以前的版本。
當(dāng)然,我們用 Vue3 就是要用它的 組合 API,組合 API中訪問這些鉤子的方式略有不同,組合API在較大的Vue項目中特別有用。
本文主要內(nèi)容:
- Vue生命周期鉤子有哪些
- 在選項API中使用 Vue 生命周期鉤子
- 在組合API中使用Vue 3生命周期鉤子
- 將 Vue2 的生命周期鉤子代碼更新到 Vue3
- 看看Vue 2和Vue 3中的每個生命周期鉤子
- 創(chuàng)建
- 掛載
- 更新
- 卸載
- 激活
- Vue 3中的新調(diào)試鉤子
Vue生命周期鉤子有哪些
首先,來看一下 選項API 和 組合 API中 Vue 3生命周期鉤子的圖表。在深入細(xì)節(jié)之前,這能加深我們的理解。
本質(zhì)上,每個主要Vue生命周期事件被分成兩個鉤子,分別在事件之前和之后調(diào)用。Vue應(yīng)用程序中有4個主要事件(8個主要鉤子)。
- 創(chuàng)建 — 在組件創(chuàng)建時執(zhí)行
- 掛載 — DOM 被掛載時執(zhí)行
- 更新 — 當(dāng)響應(yīng)數(shù)據(jù)被修改時執(zhí)行
- 銷毀 — 在元素被銷毀之前立即運行
在選項API中使用 Vue 生命周期鉤子
使用 選項API,生命周期鉤子是被暴露 Vue實例上的選項。我們不需要導(dǎo)入任何東西,只需要調(diào)用這個方法并為這個生命周期鉤子編寫代碼。
例如,假設(shè)我們想訪問mounted()和updated()生命周期鉤子,可以這么寫:
- // 選項 API
- <script>
- export default {
- mounted() {
- console.log('mounted!')
- },
- updated() {
- console.log('updated!')
- }
- }
- </script>
在組合API中使用Vue 3生命周期鉤子
在組合API中,我們需要將生命周期鉤子導(dǎo)入到項目中,才能使用,這有助于保持項目的輕量性。
- // 組合 API
- import { onMounted } from 'vue'
除了beforecate和created(它們被setup方法本身所取代),我們可以在setup方法中訪問的API生命周期鉤子有9個選項:
- onBeforeMount – 在掛載開始之前被調(diào)用:相關(guān)的 render 函數(shù)首次被調(diào)用。
- onMounted – 組件掛載時調(diào)用
- onBeforeUpdate – 數(shù)據(jù)更新時調(diào)用,發(fā)生在虛擬 DOM 打補丁之前。這里適合在更新之前訪問現(xiàn)有的 DOM,比如手動移除已添加的事件監(jiān)聽器。
- onUpdated – 由于數(shù)據(jù)更改導(dǎo)致的虛擬 DOM 重新渲染和打補丁,在這之后會調(diào)用該鉤子。
- onBeforeUnmount – 在卸載組件實例之前調(diào)用。在這個階段,實例仍然是完全正常的。
- onUnmounted – 卸載組件實例后調(diào)用。調(diào)用此鉤子時,組件實例的所有指令都被解除綁定,所有事件偵聽器都被移除,所有子組件實例被卸載。
- onActivated – 被 keep-alive 緩存的組件激活時調(diào)用。
- onDeactivated – 被 keep-alive 緩存的組件停用時調(diào)用。
- onErrorCaptured – 當(dāng)捕獲一個來自子孫組件的錯誤時被調(diào)用。此鉤子會收到三個參數(shù):錯誤對象、發(fā)生錯誤的組件實例以及一個包含錯誤來源信息的字符串。此鉤子可以返回 false 以阻止該錯誤繼續(xù)向上傳播。
使用事例:
- // 組合 API
- <script>
- import { onMounted } from 'vue'
- export default {
- setup () {
- onMounted(() => {
- console.log('mounted in the composition api!')
- })
- }
- }
- </script>
將 Vue2 的生命周期鉤子代碼更新到 Vue3
這個從Vue2 到Vue3的生命周期映射是直接從Vue 3 Composition API文檔中獲得的:
- beforeCreate -> 使用 setup()
- created -> 使用 setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
深入了解每個生命周期鉤子
我們現(xiàn)在了解了兩件重要的事情:
- 我們可以使用的不同的生命周期鉤子
- 如何在選項API和組合API中使用它們
我們深入一下每個生命周期鉤子,看看它們是如何被使用的,我們可以在每個鉤子中編寫特定代碼,來測試在Options API和Composition API中的各自的區(qū)別。
beforeCreate() – 選項 API
由于創(chuàng)建的掛鉤是用于初始化所有響應(yīng)數(shù)據(jù)和事件的事物,因此beforeCreate無法訪問組件的任何響應(yīng)數(shù)據(jù)和事件。
以下面的代碼塊為例:
- // 選項 API
- export default {
- data() {
- return {
- val: 'hello'
- }
- },
- beforeCreate() {
- console.log('Value of val is: ' + this.val)
- }
- }
val的輸出值是 undefined,因為尚未初始化數(shù)據(jù),我們也不能在這調(diào)用組件方法。
如果你想查看可用內(nèi)容的完整列表,建議只運行console.log(this)來查看已初始化的內(nèi)容。當(dāng)使用選項API時,這做法在其他鉤子中也很有用。
created() – 選項 API
如果我們要在組件創(chuàng)建時訪問組件的數(shù)據(jù)和事件,可以把上面的 beforeCreate 用 created代替。
- // 選項API
- export default {
- data() {
- return {
- val: 'hello'
- }
- },
- created() {
- console.log('Value of val is: ' + this.val)
- }
- }
其輸出為Value of val is: hello,因為我們已經(jīng)初始化了數(shù)據(jù)。
在處理讀/寫反應(yīng)數(shù)據(jù)時,使用created 的方法很有用。例如,要進行API調(diào)用然后存儲該值,則可以在此處進行此操作。
最好在這里執(zhí)行此操作,而不是在mounted 中執(zhí)行此操作,因為它發(fā)生在Vue的同步初始化過程中,并且我們需要執(zhí)行所有數(shù)據(jù)讀取/寫入操作。
那么組合API的創(chuàng)建鉤子呢?
對于使用 組合API 的 Vue3 生命周期鉤子,使用setup()方法替換beforecate和created。這意味著,在這些方法中放入的任何代碼現(xiàn)在都只在setup方法中。
- // 組合AP
- import { ref } from 'vue'
- export default {
- setup() {
- const val = ref('hello')
- console.log('Value of val is: ' + val.value)
- return {
- val
- }
- }
- }
beforeMount() and onBeforeMount()
在組件DOM實際渲染安裝之前調(diào)用。在這一步中,根元素還不存在。在選項API中,可以使用this.$els來訪問。在組合API中,為了做到這一點,必須在根元素上使用ref。
- // 選項 API
- export default {
- beforeMount() {
- console.log(this.$el)
- }
- }
組合API中使用 ref:
- // 組合 API
- <template>
- <div ref='root'>
- Hello World
- </div>
- </template>
- import { ref, onBeforeMount } from 'vue'
- export default {
- setup() {
- const root = ref(null)
- onBeforeMount(() => {
- console.log(root.value)
- })
- return {
- root
- }
- },
- beforeMount() {
- console.log(this.$el)
- }
- }
因為app.$el還沒有創(chuàng)建,所以輸出將是undefined。
mounted() and onMounted()
在組件的第一次渲染后調(diào)用,該元素現(xiàn)在可用,允許直接DOM訪問。
同樣,在 選項API中,我們可以使用this.$el來訪問我們的DOM,在組合API中,我們需要使用refs來訪問Vue生命周期鉤子中的DOM。
- import { ref, onMounted } from 'vue'
- export default {
- setup() { /* 組合 API */
- const root = ref(null)
- onMounted(() => {
- console.log(root.value)
- })
- return {
- root
- }
- },
- mounted() { /* 選項 API */
- console.log(this.$el)
- }
- }
beforeUpdate() and onBeforeUpdate()
數(shù)據(jù)更新時調(diào)用,發(fā)生在虛擬 DOM 打補丁之前。這里適合在更新之前訪問現(xiàn)有的 DOM,比如手動移除已添加的事件監(jiān)聽器。
beforeUpdate對于跟蹤對組件的編輯次數(shù),甚至跟蹤創(chuàng)建“撤消”功能的操作很有用。
updated() and onUpdated()
DOM更新后,updated的方法即會調(diào)用。
- <template>
- <div>
- <p>{{val}} | edited {{ count }} times</p>
- <button @click='val = Math.random(0, 100)'>Click to Change</button>
- </div>
- </template>
選項 API 方式:
- export default {
- data() {
- return {
- val: 0
- }
- },
- beforeUpdate() {
- console.log("beforeUpdate() val: " + this.val)
- },
- updated() {
- console.log("updated() val: " + this.val
- }
- }
組合API的方式:
- import { ref, onBeforeUpdate, onUpdated } from 'vue'
- export default {
- setup () {
- const count = ref(0)
- const val = ref(0)
- onBeforeUpdate(() => {
- count.value++;
- console.log("beforeUpdate");
- })
- onUpdated(() => {
- console.log("updated() val: " + val.value)
- })
- return {
- count, val
- }
- }
- }
這些方法很有用,但是對于更多場景,我們需要使用的watch方法檢測這些數(shù)據(jù)更改。watch 之所以好用,是因為它給出了更改后的數(shù)據(jù)的舊值和新值。
另一種選擇是使用計算屬性來基于元素更改狀態(tài)。
beforeUnmount() 和 onBeforeUnmounted()
在卸載組件實例之前調(diào)用。在這個階段,實例仍然是完全正常的。
在 選項 API中,刪除事件偵聽器的示例如下所示。
- // 選項 API
- export default {
- mounted() {
- console.log('mount')
- window.addEventListener('resize', this.someMethod);
- },
- beforeUnmount() {
- console.log('unmount')
- window.removeEventListener('resize', this.someMethod);
- },
- methods: {
- someMethod() {
- // do smth
- }
- }
- }
- // 組合API
- import { onMounted, onBeforeUnmount } from 'vue'
- export default {
- setup () {
- const someMethod = () => {
- // do smth
- }
- onMounted(() => {
- console.log('mount')
- window.addEventListener('resize', someMethod);
- })
- onBeforeUnmount(() => {
- console.log('unmount')
- window.removeEventListener('resize', someMethod);
- })
- }
- }
實際操作的一種方法是在Vite,vue-cli或任何支持熱重載的開發(fā)環(huán)境中,更新代碼時,某些組件將自行卸載并安裝。
unmounted() 和 onUnmounted()
卸載組件實例后調(diào)用。調(diào)用此鉤子時,組件實例的所有指令都被解除綁定,所有事件偵聽器都被移除,所有子組件實例被卸載。
- import { onUnmounted } from 'vue'
- export default {
- setup () { /* 組合 API */
- onUnmounted(() => {
- console.log('unmounted')
- })
- },
- unmounted() { /* 選項 API */
- console.log('unmounted')
- }
- }
activated() and onActivated()
被keep-alive 緩存的組件激活時調(diào)用。
例如,如果我們使用keep-alive組件來管理不同的選項卡視圖,每次在選項卡之間切換時,當(dāng)前選項卡將運行這個 activated 鉤子。
假設(shè)我們使用keep-alive包裝器進行以下動態(tài)組件。
- <template>
- <div>
- <span @click='tabName = "Tab1"'>Tab 1 </span>
- <span @click='tabName = "Tab2"'>Tab 2</span>
- <keep-alive>
- <component :is='tabName' class='tab-area'/>
- </keep-alive>
- </div>
- </template>
- <script>
- import Tab1 from './Tab1.vue'
- import Tab2 from './Tab2.vue'
- import { ref } from 'vue'
- export default {
- components: {
- Tab1,
- Tab2
- },
- setup () { /* 組合 API */
- const tabName = ref('Tab1')
- return {
- tabName
- }
- }
- }
- </script>
在Tab1.vue組件內(nèi)部,我們可以像這樣訪問activated鉤子。
- <template>
- <div>
- <h2>Tab 1</h2>
- <input type='text' placeholder='this content will persist!'/>
- </div>
- </template>
- <script>
- import { onActivated } from 'vue'
- export default {
- setup() {
- onActivated(() => {
- console.log('Tab 1 Activated')
- })
- }
- }
- </script>
deactivated() 和 onDeactivated()
被 keep-alive 緩存的組件停用時調(diào)用。
這個鉤子在一些用例中很有用,比如當(dāng)一個特定視圖失去焦點時保存用戶數(shù)據(jù)和觸發(fā)動畫。
- import { onActivated, onDeactivated } from 'vue'
- export default {
- setup() {
- onActivated(() => {
- console.log('Tab 1 Activated')
- })
- onDeactivated(() => {
- console.log('Tab 1 Deactivated')
- })
- }
- }
現(xiàn)在,當(dāng)我們在選項卡之間切換時,每個動態(tài)組件的狀態(tài)都將被緩存和保存。

Vue3 調(diào)試鉤子
Vue3 為我們提供了兩個可用于調(diào)試目的的鉤子。
- onRenderTracked
- onRenderTriggered
這兩個事件都帶有一個debugger event,此事件告訴你哪個操作跟蹤了組件以及該操作的目標(biāo)對象和鍵。
onRenderTracked
跟蹤虛擬 DOM 重新渲染時調(diào)用。鉤子接收 debugger event 作為參數(shù)。此事件告訴你哪個操作跟蹤了組件以及該操作的目標(biāo)對象和鍵。
- <div id="app">
- <button v-on:click="addToCart">Add to cart</button>
- <p>Cart({{ cart }})</p>
- </div>
- const app = Vue.createApp({
- data() {
- return {
- cart: 0
- }
- },
- renderTracked({ key, target, type }) {
- console.log({ key, target, type })
- /* 當(dāng)組件第一次渲染時,這將被記錄下來:
- {
- key: "cart",
- target: {
- cart: 0
- },
- type: "get"
- }
- */
- },
- methods: {
- addToCart() {
- this.cart += 1
- }
- }
- })
- app.mount('#app')
renderTracked
當(dāng)虛擬 DOM 重新渲染為 triggered.Similarly 為renderTracked,接收 debugger event作為參數(shù)。此事件告訴你是什么操作觸發(fā)了重新渲染,以及該操作的目標(biāo)對象和鍵。
用法:
- <div id="app">
- <button v-on:click="addToCart">Add to cart</button>
- <p>Cart({{ cart }})</p>
- </div>
- const app = Vue.createApp({
- data() {
- return {
- cart: 0
- }
- },
- renderTriggered({ key, target, type }) {
- console.log({ key, target, type })
- },
- methods: {
- addToCart() {
- this.cart += 1
- /* 這將導(dǎo)致renderTriggered調(diào)用
- {
- key: "cart",
- target: {
- cart: 1
- },
- type: "set"
- }
- */
- }
- }
- })
- app.mount('#app')
總結(jié)
無論你選擇使用選項API還是 組合API,不僅要知道要使用哪個生命周期掛鉤,而且要知道為什么要使用它,這一點很重要。
對于許多問題,可以使用多個生命周期鉤子。但是最好知道哪個是最適合你用例的。無論如何,你都應(yīng)該好好考慮一下,并有充分的理由去選擇一個特定的生命周期鉤子。
我希望這能幫助大家更多地理解生命周期鉤子以及如何在大家的項目中實現(xiàn)它們。