三大框架中實例化組件并渲染組件視圖時,組件實例的生命周期就開始了。生命周期一直伴隨著變更檢測,框架會檢查數(shù)據(jù)綁定屬性何時發(fā)生變化,并按需更新視圖和組件實例。當從應用中銷毀組件實例并從 DOM 中移除它渲染的模板時,生命周期就結(jié)束了。
我們的應用可以使用生命周期鉤子方法來觸發(fā)組件生命周期中的關鍵事件,以初始化新實例,需要時啟動變更檢測,在變更檢測過程中響應更新,并在刪除實例之前進行清理。
三大框架關于組件生命周期鉤子各不相同,但是對于我們來說最常用到的是掛載、更新和卸載這幾個鉤子,本章將對這三個鉤子來做比較。
Angular 組件生命周期鉤子
在這里,我們先列出 Angular 組件生命周期中的鉤子,下表中的關于鉤子的次序就是組件在生命周期中的執(zhí)行順序。
| 鉤子方法 | 用途 | 時機 |
|---|---|---|
| ngOnChanges | 當 Angular 設置或重新設置數(shù)據(jù)綁定的輸入屬性時響應。該方法接受當前和上一屬性值的對象。 | 如果組件綁定過輸入屬性,那么在 ngOnInit() 之前以及所綁定的一個或多個輸入屬性的值發(fā)生變化時都會調(diào)用。 |
| ngOnInit | 在 Angular 第一次顯示數(shù)據(jù)綁定和設置組件的輸入屬性之后,初始化組件。 | 在第一輪 ngOnChanges() 完成之后調(diào)用,只調(diào)用一次。而且即使沒有調(diào)用過 ngOnChanges(),也仍然會調(diào)用 ngOnInit()(比如當模板中沒有綁定任何輸入屬性時)。 |
| ngDoCheck | 檢測,并在發(fā)生 Angular 無法或不愿意自己檢測的變化時作出反應。 | 緊跟在每次執(zhí)行變更檢測時的 ngOnChanges() 和 首次執(zhí)行變更檢測時的 ngOnInit() 后調(diào)用。 |
| ngAfterContentInit | 當 Angular 把外部內(nèi)容投影進組件視圖或指令所在的視圖之后調(diào)用。 | 第一次 ngDoCheck() 之后調(diào)用,只調(diào)用一次。 |
| ngAfterContentChecked | 每當 Angular 檢查完被投影到組件中的內(nèi)容之后調(diào)用。 | ngAfterContentInit() 和每次 ngDoCheck() 之后調(diào)用。 |
| ngAfterViewInit | 當 Angular 初始化完組件視圖及其子視圖調(diào)用。 | 第一次 ngAfterContentChecked() 之后調(diào)用,只調(diào)用一次。 |
| ngAfterViewChecked | 每當 Angular 做完組件視圖和子視圖或包含該指令的視圖的變更檢測之后調(diào)用。 | ngAfterViewInit() 和每次 ngAfterContentChecked() 之后調(diào)用。 |
| ngOnDestroy | 每當 Angular 每次銷毀組件后調(diào)用并清掃。在這兒取消訂閱可觀察對象和事件綁定,以防內(nèi)存泄漏。 | 在 Angular 銷毀指令或組件后立即調(diào)用。 |
在 Angular 的組件生命周期中并沒有稱之為掛載、更新和卸載的鉤子,為了更方便的在三個框架中做對比,我們把它生命周期中類似于這幾個功能的鉤子稱之為掛載、更新和卸載。
掛載鉤子
ngAfterViewInit 鉤子會在完全初始化了組件的視圖后調(diào)用,它只會調(diào)用一次。這個鉤子中沒有參數(shù)。
更新鉤子
ngOnChanges 鉤子會在組件的輸入屬性發(fā)生了變化時調(diào)用,它會在組件初始化之前先調(diào)用一次。ngOnChanges() 方法獲取了一個對象,它把每個發(fā)生變化的屬性名都映射到了一個 SimpleChange 對象,該對象中有屬性的當前值和前一個值。這個鉤子會在這些發(fā)生了變化的屬性上進行迭代,并記錄它們。
如果組件并沒有綁定任何輸入屬性,那么此鉤子不會被調(diào)用。
需要注意的是在 Angular 組件的生命周期中并沒有組件自身屬性變化后可調(diào)用的鉤子,如果要監(jiān)測組件自身屬性的變化,可以使用 RxJS 庫來創(chuàng)建和訂閱可觀察的對象。
卸載鉤子
ngOnDestroy 鉤子會在組件實例被銷毀后調(diào)用,這個鉤子中沒有參數(shù)。
React 組件生命周期鉤子
和其他兩個框架不一樣,在 React 的函數(shù)式組件中并沒有可調(diào)用的生命周期鉤子。想要實現(xiàn)類似掛載、更新和卸載功能的鉤子可以使用 useEffect 鉤子。
需要注意的是,在編寫和讀取 Effect 時,要獨立地考慮每個 Effect(如何開始和停止同步),而不是從組件的角度思考(如何掛載、更新或卸載)。在本章中是為了與其他兩個框架做比較,才從組件的角度來使用 Effect 的。
在這里我們提供一段使用 useEffct 來實現(xiàn)上述三個鉤了的示例。
import { useState, useEffect } from 'react';
export default function Clock({ time }) {
const [index, setIndex] = useState(0);
useEffect(() => {
// 【更新鉤子】當props與state變更、組件渲染后執(zhí)行
});
useEffect(() => {
// 【掛載鉤子】組件渲染后僅執(zhí)行一次
return () => {
// 【卸載鉤子】組件在卸載后執(zhí)行
}
}, []);
useEffect(() => {
// 【更新鉤子】僅在index變更,組件渲染后執(zhí)行
}, [index]);
function handleClick() {
setIndex(index + 1);
}
return (
<>
<button onClick={handleClick}>
Next
</button>
<p>{index}</p>
<div>{time}</div>
<>
);
}
掛載鉤子
使用 useEffect 鉤子,第二個參數(shù)傳遞 [] 時,會在組件渲染后僅調(diào)用一次這個鉤子。
useEffect(() => {
// 【掛載鉤子】組件渲染后僅執(zhí)行一次
}, []);
更新鉤子
使用 useEffect 鉤子,不傳遞第二個參數(shù)時,會在 props 或 state 發(fā)生變更、組件渲染后調(diào)用這個鉤子。
useEffect(() => {
// 【更新鉤子】當props與state變更、組件渲染后執(zhí)行
});
卸載鉤子
在 useEffect 鉤子中添加返回的函數(shù)會在組件被卸載后執(zhí)行。
useEffect(() => {
// 【卸載鉤子】組件在卸載后執(zhí)行
});
Vue組件生命周期鉤子
在這里,我們先列出 Vue 組件生命周期中的鉤子,下表中的關于鉤子的次序就是組件在生命周期中的執(zhí)行順序。這些鉤子都應該在組件的 setup() 階段被同步調(diào)用。
| 鉤子方法 | 用途 | 時機 |
|---|---|---|
| onBeforeMount | 當這個鉤子被調(diào)用時,組件已經(jīng)完成了其響應式狀態(tài)的設置,但還沒有創(chuàng)建 DOM 節(jié)點。它即將首次執(zhí)行 DOM 渲染過程。 | 在組件被掛載之前被調(diào)用。 |
| onMounted | 這個鉤子通常用于執(zhí)行需要訪問組件所渲染的 DOM 樹相關的副作用。 | 在組件掛載完成后執(zhí)行。 |
| onBeforeUpdate | 這個鉤子可以用來在 Vue 更新 DOM 之前訪問 DOM 狀態(tài)。在這個鉤子中更改狀態(tài)也是安全的。 | 在組件即將因為響應式狀態(tài)變更而更新其 DOM 樹之前調(diào)用。 |
| onUpdated | 這個鉤子會在組件的任意 DOM 更新后被調(diào)用,這些更新可能是由不同的狀態(tài)變更導致的,因為多個狀態(tài)變更可以在同一個渲染周期中批量執(zhí)行(考慮到性能因素)。 | 在組件因為響應式狀態(tài)變更而更新其 DOM 樹之后調(diào)用。 |
| onBeforeUnmount | 當這個鉤子被調(diào)用時,組件實例依然還保有全部的功能。 | 在組件實例被卸載之前調(diào)用。 |
| onUnmounted | 可以在這個鉤子中手動清理一些副作用,例如計時器、DOM 事件監(jiān)聽器或者與服務器的連接。 | 在組件實例被卸載之后調(diào)用。 |
掛載鉤子
onMounted 鉤子在組件掛載完成后調(diào)用,它只會執(zhí)行一次。
更新鉤子
onUpdated 鉤子在組件因為響應式狀態(tài)變更而更新其 DOM 樹之后調(diào)用。
卸載鉤子
onUnmounted 鉤子在組件實例被卸載之后調(diào)用。
小結(jié)
本章介紹了三大框架組件的生命周期中的鉤子,對掛載、更新與卸載鉤子做了重點說明。
Angular 中是類組件,實例化的組件可以理解成一個類的對象,把組件實例生命周期的鉤子當成對象的方法考慮會更好理解。
React 函數(shù)組件中并沒有可以調(diào)用的生命周期鉤子,在編寫和讀取 Effect 時,要獨立地考慮每個 Effect(如何開始和停止同步),而不是從組件的角度思考(如何掛載、更新或卸載)。
Vue 組件生命周期中關于掛載、更新和卸載的鉤子符合我們的直觀描述,更好理解一些。
文章參考鏈接:
- https://angular.cn/guide/lifecycle-hooks
- https://angular.cn/api/core/OnChanges
- https://angular.cn/api/core/AfterViewInit
- https://zh-hans.react.dev/reference/react/useEffect
- https://zh-hans.react.dev/learn/synchronizing-with-effects
- https://zh-hans.react.dev/learn/lifecycle-of-reactive-effects
- https://zh-hans.react.dev/reference/react/useEffect
- https://cn.vuejs.org/guide/essentials/lifecycle.html
- https://cn.vuejs.org/api/composition-api-lifecycle.html