2024-09-19|閱讀時間 ‧ 約 22 分鐘

EP11 - 生命週期鉤子

Lifecycle Hooks,感覺蠻有趣~生命週期有哪些狀態?
照理說應該會看到一個流程圖的樣子~
對於生命週期都會覺得比較常用通常是某幾個階段~
我只想要Destroy這個生命週期~www

每個 Vue 組件實例在創建時會經歷一系列的初始化步驟例。例如,它需要設置數據觀測編譯模板將實例掛載到 DOM 上,並在數據變更時更新 DOM。在這些過程中,它會執行稱為生命週期鉤子函數,這些鉤子允許用戶在特定階段插入自己的代碼。

註冊生命週期鉤子 - Registering Lifecycle Hooks

例如,onMounted 鉤子可以在組件完成初始渲染並創建 DOM 節點後運行代碼:

<script setup>
import { onMounted } from 'vue'

onMounted(() => {
console.log(`組件現在已經掛載。`)
})
</script>

還有其他鉤子會在實例的不同生命週期階段被調用,其中最常用的是 onMountedonUpdatedonUnmounted

調用 onMounted 時,Vue 會自動將註冊的回調函數與當前活躍的組件實例關聯起來。這要求這些鉤子必須在組件設置期間同步註冊。例如,不要這樣做:

setTimeout(() => {
onMounted(() => {
// 這樣做不會奏效。
})
}, 100)

注意,這並不意味著調用必須放置在 setup()<script setup> 內部。onMounted() 可以在外部函數中調用,只要調用堆棧是同步的,並且源自 setup() 內部即可。

Q:為什麼不會奏效?

在 Vue 中,onMounted 鉤子需要在組件的設置階段同步註冊。這是因為 Vue 需要在組件實例的生命週期內部確保所有的鉤子都已正確註冊。如果你在非同步操作中(例如 setTimeout)調用 onMounted,這樣的註冊可能會在組件的生命週期階段之外進行,因此不會如預期地工作。

  1. setTimeout 是一個非同步操作,它會在指定的時間(這裡是 100 毫秒)後執行其回調函數。
  2. onMounted 鉤子的註冊會被延遲到 setTimeout 的回調中。由於 setTimeout 是非同步的,這可能會在 Vue 的組件實例已經完成設置之後執行,這時組件的生命週期已經不再適合進行這些鉤子的註冊。
  3. 因此,onMounted 的回調函數可能無法正確地與組件實例關聯,從而導致該鉤子不會如預期執行。
要確保 onMounted 鉤子能正常工作,它應該在組件的設置過程中同步註冊,而不是在非同步操作中。這樣 Vue 可以在適當時間點觸發這些鉤子,保證能正確地執行。

Vue 組件實例的生命周期圖 - Lifecycle Diagram​

下圖展示了 Vue 組件實例的生命周期。在你剛開始學習時,可能不需要完全理解其中的每個細節,但隨著你學習和構建更多的項目,它將成為一個有用的參考工具。

常見的生命周期鉤子

  • onBeforeMount: 當組件即將被掛載到 DOM 上時調用。此時,虛擬 DOM 已經渲染完畢,但真實的 DOM 還沒有更新。
  • onMounted: 組件已經掛載到 DOM 上時調用。此時,DOM 已經可用,可以進行 DOM 操作。
  • onBeforeUpdate: 當組件的響應式數據發生變化並即將更新 DOM 之前調用。可以在這裡查看更新前的狀態。
  • onUpdated: 組件的 DOM 更新完成後調用。此時可以獲取到最新的 DOM 狀態。
  • onBeforeUnmount: 組件即將從 DOM 上移除時調用。可以在這裡進行一些清理工作。
  • onUnmounted: 組件已經從 DOM 上移除後調用。此時組件的所有響應式數據、監聽器等都已經被銷毀。

其他生命周期鉤子

除了上面提到的常見鉤子外,還有一些其他鉤子在特定場景下非常有用:

  • onActivated: 當組件從 <keep-alive> 緩存中激活時調用。
  • onDeactivated: 當組件被 <keep-alive> 緩存時調用。
  • onErrorCaptured: 當捕獲到組件內部的錯誤時調用,用於錯誤邊界處理。
<template>
<div>
<p>Component Lifecycle Hooks Example</p>
</div>
</template>

<script setup>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onActivated, onDeactivated, onErrorCaptured } from 'vue'

onBeforeMount(() => {
console.log('beforeMount - 組件即將掛載')
})

onMounted(() => {
console.log('mounted - 組件已掛載')
})

onBeforeUpdate(() => {
console.log('beforeUpdate - 組件即將更新')
})

onUpdated(() => {
console.log('updated - 組件已更新')
})

onBeforeUnmount(() => {
console.log('beforeUnmount - 組件即將銷毀')
})

onUnmounted(() => {
console.log('unmounted - 組件已銷毀')
})

onActivated(() => {
console.log('activated - 組件已激活')
})

onDeactivated(() => {
console.log('deactivated - 組件已停用')
})

onErrorCaptured((err, instance, info) => {
console.error('errorCaptured - 捕獲到錯誤:', err, info)
})
</script>
beforeMount - 組件即將掛載
mounted - 組件已掛載
beforeUpdate - 組件即將更新
updated - 組件已更新
activated - 組件已激活
deactivated - 組件已停用
beforeUnmount - 組件即將銷毀
unmounted - 組件已銷毀
errorCaptured - 捕獲到錯誤: ...

請注意,如果你的組件沒有使用 <keep-alive>,則 activateddeactivated 鉤子不會觸發。如果你的組件或其子組件沒有拋出錯誤,則 errorCaptured 鉤子也不會觸發。

若要了解所有生命周期鉤子及其相應的使用案例,請查閱 Vue 的生命周期鉤子 API 參考文件。這些鉤子允許你在 Vue 組件實例的不同階段執行自定義代碼,使你能夠更靈活地控制組件的行為。

Q:什麼是<keep-alive>?

<keep-alive> 是 Vue 提供的一個內建組件,用於包裹動態組件,從而在其激活和停用之間保留組件的狀態或避免重新渲染。這在需要在不同視圖之間切換時非常有用,例如導航標籤或多頁應用。

生命週期鉤子的作用

  1. activated 鉤子
    • 當組件被 <keep-alive> 包裹且激活時(即重新顯示)會觸發。
    • 可以在這裡執行一些初始化工作或重新設置一些狀態。
  2. deactivated 鉤子
    • 當組件被 <keep-alive> 包裹且停用時(即隱藏)會觸發。
    • 可以在這裡保存狀態或執行一些清理工作。

使用示例

假設有一個包含兩個子組件的 Vue 應用,每個子組件會在不同的選項卡中顯示。我們使用 <keep-alive> 來包裹這些子組件,這樣在切換選項卡時不會重新創建和銷毀組件,而是保留其狀態。

<template>
<div>
<button @click="currentView = 'view1'">View 1</button>
<button @click="currentView = 'view2'">View 2</button>

<keep-alive>
<component :is="currentView"></component>
</keep-alive>
</div>
</template>

<script setup>
import { ref } from 'vue'
import View1 from './View1.vue'
import View2 from './View2.vue'

const currentView = ref('view1')
</script>

有兩個按鈕,分別用來切換當前顯示的視圖。

  1. @click="currentView = 'view1'":點擊這個按鈕會將 currentView 設置為 'view1'
  2. @click="currentView = 'view2'":點擊這個按鈕會將 currentView 設置為 'view2'
  3. <keep-alive> 是一個 Vue 的內建組件,用來包裹動態組件。它會在組件激活和停用之間保持其狀態,不會每次都重新創建和銷毀組件。
  4. <component :is="currentView"></component><component> 是一個動態組件,可以根據 currentView 的值來渲染不同的組件。currentView 的值決定了哪個組件會被渲染。
  5. :is 是一個綁定屬性,用來指定要渲染的組件名稱。這個屬性可以接受一個字符串或一個組件對象。

子組件View1.vue中的 activateddeactivated

<template>
<div>
<p>This is View 1</p>
</div>
</template>

<script setup>
import { onActivated, onDeactivated } from 'vue'

onActivated(() => {
console.log('View 1 is activated')
})

onDeactivated(() => {
console.log('View 1 is deactivated')
})
</script>

類似的子組件 View2.vue

<template>
<div>
<p>This is View 2</p>
</div>
</template>

<script setup>
import { onActivated, onDeactivated } from 'vue'

onActivated(() => {
console.log('View 2 is activated')
})

onDeactivated(() => {
console.log('View 2 is deactivated')
})
</script>

運行結果

當你在選項卡之間切換時,你會在控制台看到類似以下的輸出:

View 1 is activated
View 1 is deactivated
View 2 is activated
View 2 is deactivated
生命週期這玩意常常搞得我一個頭兩個大~
每個鉤子放log,看看每個週期的先後順序跟邏輯
然後就是觀察自動同步的時候是哪個週期?

Q:自動同步在哪個週期?

<template>
<div>
<p>Component Lifecycle Hooks Logging Example</p>
<button @click="updateMessage">Update Message</button>
<p>{{ message }}</p>
</div>
</template>

<script setup>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'

const message = ref('Hello, Vue!')

// 生命週期鉤子放置 log
onBeforeMount(() => {
console.log('beforeMount - 組件即將掛載')
})

onMounted(() => {
console.log('mounted - 組件已掛載')
})

onBeforeUpdate(() => {
console.log('beforeUpdate - 組件即將更新')
})

onUpdated(() => {
console.log('updated - 組件已更新')
})

onBeforeUnmount(() => {
console.log('beforeUnmount - 組件即將銷毀')
})

onUnmounted(() => {
console.log('unmounted - 組件已銷毀')
})

// 更新消息的方法
const updateMessage = () => {
message.value = 'Message updated!'
}
</script>

在上述示例中,當點擊按鈕更新消息時,可以看到 beforeUpdateupdated 鉤子的觸發順序。這表明在數據變化後,Vue 開始自動同步 DOM,在此期間 beforeUpdate 鉤子被觸發,DOM 更新完成後 updated 鉤子被觸發。

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.