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