React Native 中組件的生命周期
概述
每個(gè)組件都包含“生命周期方法”,你可以重寫這些方法,以便于在運(yùn)行過程中特定的階段執(zhí)行這些方法。所謂生命周期,就是一個(gè)對(duì)象從開始生成到最后消亡所經(jīng)歷的狀態(tài),理解生命周期,是合理開發(fā)的關(guān)鍵。
可以把組件生命周期大致分為三個(gè)階段:
-
第一階段:Mounting(掛載階段)
是組件第一次繪制階段,在這里完成了組件的加載和初始化;
-
第二階段:Updating(更新階段)
是組件在運(yùn)行和交互階段,這個(gè)階段組件可以處理用戶交互,或者接收事件更新界面;
-
第三階段:Unmounting(卸載階段)
是組件卸載消亡的階段,這里做一些組件的清理工作。
1. 掛載階段
當(dāng)組件實(shí)例被創(chuàng)建并插入 DOM 中時(shí),其生命周期調(diào)用順序如下:
1. cunstructor()
在 React 組件掛載之前,會(huì)調(diào)用它的構(gòu)造函數(shù)。在為 React.Component 子類實(shí)現(xiàn)構(gòu)造函數(shù)時(shí),應(yīng)在其他語(yǔ)句之前前調(diào)用 super(props)。否則,this.props 在構(gòu)造函數(shù)中可能會(huì)出現(xiàn)未定義的 bug。
通常,在 React 中,構(gòu)造函數(shù)僅用于以下兩種情況:
- 通過給
this.state賦值對(duì)象來初始化內(nèi)部 state。 - 為事件處理函數(shù)綁定實(shí)例
2. getDerivedStateFromProps() 不常用
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps 會(huì)在調(diào)用 render 方法之前調(diào)用,并且在初始掛載及后續(xù)更新時(shí)都會(huì)被調(diào)用。它應(yīng)返回一個(gè)對(duì)象來更新 state,如果返回 null 則不更新任何內(nèi)容。
此方法適用于罕見的用例,即 state 的值在任何時(shí)候都取決于 props。例如,實(shí)現(xiàn) <Transition> 組件可能很方便,該組件會(huì)比較當(dāng)前組件與下一組件,以決定針對(duì)哪些組件進(jìn)行轉(zhuǎn)場(chǎng)動(dòng)畫。
派生狀態(tài)會(huì)導(dǎo)致代碼冗余,并使組件難以維護(hù)。 確保你已熟悉這些簡(jiǎn)單的替代方案:
- 如果你需要執(zhí)行副作用(例如,數(shù)據(jù)提取或動(dòng)畫)以響應(yīng) props 中的更改,請(qǐng)改用
componentDidUpdate。 - 如果只想在 prop 更改時(shí)重新計(jì)算某些數(shù)據(jù),請(qǐng)使用 memoization helper 代替。
- 如果你想在 prop 更改時(shí)“重置”某些 state,請(qǐng)考慮使組件完全受控或使用
key使組件完全不受控 代替。
3. render()
render()
render() 方法是 class 組件中唯一必須實(shí)現(xiàn)的方法。
當(dāng) render 被調(diào)用時(shí),它會(huì)檢查 this.props 和 this.state 的變化并返回以下類型之一:
-
React 元素。通常通過 JSX 創(chuàng)建。例如,
<div />會(huì)被 React 渲染為 DOM 節(jié)點(diǎn),<MyComponent />會(huì)被 React 渲染為自定義組件,無論是<div />還是<MyComponent />均為 React 元素。 - 數(shù)組或 fragments。 使得 render 方法可以返回多個(gè)元素。欲了解更多詳細(xì)信息,請(qǐng)參閱 fragments 文檔。
- Portals。可以渲染子節(jié)點(diǎn)到不同的 DOM 子樹中。欲了解更多詳細(xì)信息,請(qǐng)參閱有關(guān) portals 的文檔
- 字符串或數(shù)值類型。它們?cè)?DOM 中會(huì)被渲染為文本節(jié)點(diǎn)
-
布爾類型或 null。什么都不渲染。(主要用于支持返回
test && <Child />的模式,其中 test 為布爾類型。)
render() 函數(shù)應(yīng)該為純函數(shù),這意味著在不修改組件 state 的情況下,每次調(diào)用時(shí)都返回相同的結(jié)果,并且它不會(huì)直接與瀏覽器交互。
如需與瀏覽器進(jìn)行交互,請(qǐng)?jiān)?componentDidMount() 或其他生命周期方法中執(zhí)行你的操作。保持 render() 為純函數(shù),可以使組件更容易思考。
注意
如果
shouldComponentUpdate()返回 false,則不會(huì)調(diào)用render()。
4. componentDidMount()
componentDidMount() 會(huì)在組件掛載后(插入 DOM 樹中)立即調(diào)用。依賴于 DOM 節(jié)點(diǎn)的初始化應(yīng)該放在這里。如需通過網(wǎng)絡(luò)請(qǐng)求獲取數(shù)據(jù),此處是實(shí)例化請(qǐng)求的好地方。
這個(gè)方法是比較適合添加訂閱的地方。如果添加了訂閱,請(qǐng)不要忘記在 componentWillUnmount() 里取消訂閱
你可以在 componentDidMount() 里直接調(diào)用 setState()。它將觸發(fā)額外渲染,但此渲染會(huì)發(fā)生在瀏覽器更新屏幕之前。如此保證了即使在 render() 兩次調(diào)用的情況下,用戶也不會(huì)看到中間狀態(tài)。請(qǐng)謹(jǐn)慎使用該模式,因?yàn)樗鼤?huì)導(dǎo)致性能問題。通常,你應(yīng)該在 constructor() 中初始化 state。如果你的渲染依賴于 DOM 節(jié)點(diǎn)的大小或位置,比如實(shí)現(xiàn) modals 和 tooltips 等情況下,你可以使用此方式處理
2. 更新階段
當(dāng)組件的 props 或 state 發(fā)生變化時(shí)會(huì)觸發(fā)更新。組件更新的生命周期調(diào)用順序如下:
static getDerivedStateFromProps()shouldComponentUpdate()- render()
getSnapshotBeforeUpdate()- componentDidUpdate()
1. getDerivedStateFromProps() 不常用
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps 會(huì)在調(diào)用 render 方法之前調(diào)用,并且在初始掛載及后續(xù)更新時(shí)都會(huì)被調(diào)用。它應(yīng)返回一個(gè)對(duì)象來更新 state,如果返回 null 則不更新任何內(nèi)容。
此方法適用于罕見的用例,即 state 的值在任何時(shí)候都取決于 props。例如,實(shí)現(xiàn) <Transition> 組件可能很方便,該組件會(huì)比較當(dāng)前組件與下一組件,以決定針對(duì)哪些組件進(jìn)行轉(zhuǎn)場(chǎng)動(dòng)畫。
派生狀態(tài)會(huì)導(dǎo)致代碼冗余,并使組件難以維護(hù)。 確保你已熟悉這些簡(jiǎn)單的替代方案:
- 如果你需要執(zhí)行副作用(例如,數(shù)據(jù)提取或動(dòng)畫)以響應(yīng) props 中的更改,請(qǐng)改用
componentDidUpdate。 - 如果只想在 prop 更改時(shí)重新計(jì)算某些數(shù)據(jù),請(qǐng)使用 memoization helper 代替。
- 如果你想在 prop 更改時(shí)“重置”某些 state,請(qǐng)考慮使組件完全受控或使用
key使組件完全不受控 代替。
2. shouldComponentUpdate() 不常用
shouldComponentUpdate(nextProps, nextState)
根據(jù) shouldComponentUpdate() 的返回值,判斷 React 組件的輸出是否受當(dāng)前 state 或 props 更改的影響。默認(rèn)行為是 state 每次發(fā)生變化組件都會(huì)重新渲染。大部分情況下,你應(yīng)該遵循默認(rèn)行為。
當(dāng) props 或 state 發(fā)生變化時(shí),shouldComponentUpdate() 會(huì)在渲染執(zhí)行之前被調(diào)用。返回值默認(rèn)為 true。首次渲染或使用 forceUpdate() 時(shí)不會(huì)調(diào)用該方法。
此方法僅作為性能優(yōu)化的方式而存在。不要企圖依靠此方法來“阻止”渲染,因?yàn)檫@可能會(huì)產(chǎn)生 bug。你應(yīng)該考慮使用內(nèi)置的 PureComponent 組件,而不是手動(dòng)編寫 shouldComponentUpdate()。PureComponent 會(huì)對(duì) props 和 state 進(jìn)行淺層比較,并減少了跳過必要更新的可能性。
如果你一定要手動(dòng)編寫此函數(shù),可以將 this.props 與 nextProps 以及 this.state 與nextState 進(jìn)行比較,并返回 false 以告知 React 可以跳過更新。請(qǐng)注意,返回 false 并不會(huì)阻止子組件在 state 更改時(shí)重新渲染。
我們不建議在 shouldComponentUpdate() 中進(jìn)行深層比較或使用 JSON.stringify()。這樣非常影響效率,且會(huì)損害性能。
目前,如果 shouldComponentUpdate() 返回 false,則不會(huì)調(diào)用 UNSAFE_componentWillUpdate(),render() 和 componentDidUpdate()。后續(xù)版本,React 可能會(huì)將 shouldComponentUpdate 視為提示而不是嚴(yán)格的指令,并且,當(dāng)返回 false 時(shí),仍可能導(dǎo)致組件重新渲染。
3. render()
render()
render() 方法是 class 組件中唯一必須實(shí)現(xiàn)的方法。
當(dāng) render 被調(diào)用時(shí),它會(huì)檢查 this.props 和 this.state 的變化并返回以下類型之一:
-
React 元素。通常通過 JSX 創(chuàng)建。例如,
<div />會(huì)被 React 渲染為 DOM 節(jié)點(diǎn),<MyComponent />會(huì)被 React 渲染為自定義組件,無論是<div />還是<MyComponent />均為 React 元素。 - 數(shù)組或 fragments。 使得 render 方法可以返回多個(gè)元素。欲了解更多詳細(xì)信息,請(qǐng)參閱 fragments 文檔。
- Portals。可以渲染子節(jié)點(diǎn)到不同的 DOM 子樹中。欲了解更多詳細(xì)信息,請(qǐng)參閱有關(guān) portals 的文檔
- 字符串或數(shù)值類型。它們?cè)?DOM 中會(huì)被渲染為文本節(jié)點(diǎn)
-
布爾類型或 null。什么都不渲染。(主要用于支持返回
test && <Child />的模式,其中 test 為布爾類型。)
render() 函數(shù)應(yīng)該為純函數(shù),這意味著在不修改組件 state 的情況下,每次調(diào)用時(shí)都返回相同的結(jié)果,并且它不會(huì)直接與瀏覽器交互。
如需與瀏覽器進(jìn)行交互,請(qǐng)?jiān)?componentDidMount() 或其他生命周期方法中執(zhí)行你的操作。保持 render() 為純函數(shù),可以使組件更容易思考。
注意
如果
shouldComponentUpdate()返回 false,則不會(huì)調(diào)用render()。
4. getSnapshotBeforeUpdate() 不常用
getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate() 在最近一次渲染輸出(提交到 DOM 節(jié)點(diǎn))之前調(diào)用。它使得組件能在發(fā)生更改之前從 DOM 中捕獲一些信息(例如,滾動(dòng)位置)。此生命周期的任何返回值將作為參數(shù)傳遞給 componentDidUpdate()。
此用法并不常見,但它可能出現(xiàn)在 UI 處理中,如需要以特殊方式處理滾動(dòng)位置的聊天線程等。
應(yīng)返回 snapshot 的值(或 null)。
5. componentDidUpdate()
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate() 會(huì)在更新后會(huì)被立即調(diào)用。首次渲染不會(huì)執(zhí)行此方法。
當(dāng)組件更新后,可以在此處對(duì) DOM 進(jìn)行操作。如果你對(duì)更新前后的 props 進(jìn)行了比較,也可以選擇在此處進(jìn)行網(wǎng)絡(luò)請(qǐng)求。(例如,當(dāng) props 未發(fā)生變化時(shí),則不會(huì)執(zhí)行網(wǎng)絡(luò)請(qǐng)求)。
你也可以在 componentDidUpdate() 中直接調(diào)用 setState(),但請(qǐng)注意它必須被包裹在一個(gè)條件語(yǔ)句里,正如上述的例子那樣進(jìn)行處理,否則會(huì)導(dǎo)致死循環(huán)。它還會(huì)導(dǎo)致額外的重新渲染,雖然用戶不可見,但會(huì)影響組件性能。不要將 props “鏡像”給 state,請(qǐng)考慮直接使用 props。 欲了解更多有關(guān)內(nèi)容,請(qǐng)參閱為什么 props 復(fù)制給 state 會(huì)產(chǎn)生 bug。
如果組件實(shí)現(xiàn)了 getSnapshotBeforeUpdate() 生命周期(不常用),則它的返回值將作為 componentDidUpdate() 的第三個(gè)參數(shù) “snapshot” 參數(shù)傳遞。否則此參數(shù)將為 undefined。
注意
如果
shouldComponentUpdate()返回值為 false,則不會(huì)調(diào)用componentDidUpdate()。
3. 卸載階段
當(dāng)組件從 DOM 中移除時(shí)會(huì)調(diào)用如下方法:
componentWillUnmount()
componentWillUnmount() 會(huì)在組件卸載及銷毀之前直接調(diào)用。在此方法中執(zhí)行必要的清理操作,例如,清除 timer,取消網(wǎng)絡(luò)請(qǐng)求或清除在 componentDidMount() 中創(chuàng)建的訂閱等。
componentWillUnmount() 中不應(yīng)調(diào)用 setState(),因?yàn)樵摻M件將永遠(yuǎn)不會(huì)重新渲染。組件實(shí)例卸載后,將永遠(yuǎn)不會(huì)再掛載它。
4. 錯(cuò)誤處理
當(dāng)渲染過程,生命周期,或子組件的構(gòu)造函數(shù)中拋出錯(cuò)誤時(shí),會(huì)調(diào)用如下方法:
static getDerivedStateFromError()
static getDerivedStateFromError(error)
此生命周期會(huì)在后代組件拋出錯(cuò)誤后被調(diào)用。 它將拋出的錯(cuò)誤作為參數(shù),并返回一個(gè)值以更新 state
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) { // 更新 state 使下一次渲染可以顯降級(jí) UI return { hasError: true }; }
render() {
if (this.state.hasError) { // 你可以渲染任何自定義的降級(jí) UI return <h1>Something went wrong.</h1>; }
return this.props.children;
}
}
注意
getDerivedStateFromError()會(huì)在渲染階段調(diào)用,因此不允許出現(xiàn)副作用。 如遇此類情況,請(qǐng)改用componentDidCatch()。
componentDidCatch()
componentDidCatch(error, info)
此生命周期在后代組件拋出錯(cuò)誤后被調(diào)用。 它接收兩個(gè)參數(shù):
-
error—— 拋出的錯(cuò)誤。 -
info—— 帶有componentStackkey 的對(duì)象,其中包含有關(guān)組件引發(fā)錯(cuò)誤的棧信息。
componentDidCatch() 會(huì)在“提交”階段被調(diào)用,因此允許執(zhí)行副作用。 它應(yīng)該用于記錄錯(cuò)誤之類的情況:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染可以顯示降級(jí) UI
return { hasError: true };
}
componentDidCatch(error, info) { // "組件堆棧" 例子: // in ComponentThatThrows (created by App) // in ErrorBoundary (created by App) // in div (created by App) // in App logComponentStackToMyService(info.componentStack); }
render() {
if (this.state.hasError) {
// 你可以渲染任何自定義的降級(jí) UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
注意
如果發(fā)生錯(cuò)誤,你可以通過調(diào)用
setState使用componentDidCatch()渲染降級(jí) UI,但在未來的版本中將不推薦這樣做。 可以使用靜態(tài)getDerivedStateFromError()來處理降級(jí)渲染。
總結(jié)
React的組件的完整的生命都介紹完了,把生命周期的回調(diào)函數(shù)總結(jié)成如下表格:
| 生命周期 | 調(diào)用次數(shù) |
|---|---|
| constructor() | 1(全局調(diào)用一次) |
| getDerivedStateFromProps() | >1 |
| render() | >=1 |
| componentDidMount() | 1 |
| shouldComponentUpdate() | >=0 |
| getSnapshotBeforeUpdate() | >=0 |
| componentDidUpdate() | >=0 |
| componentWillUnmount() | 1 |