組件的生命周期
每個組件都包含 “生命周期方法”,你可以重寫這些方法,以便于在運行過程中特定的階段執行這些方法。你可以使用此生命周期圖譜作為速查表。在下述列表中,常用的生命周期方法會被加粗。其余生命周期函數的使用則相對罕見。
掛載
當組件實例被創建并插入 DOM 中時,其生命周期調用順序如下:
1. constructor() 2. static getDerivedStateFromProps() 3. render()4. componentDidMount()
注意:
下述生命周期方法即將過時,在新代碼中應該避免使用它們:
UNSAFE_componentWillMount()
更新
當組件的 props 或 state 發生變化時會觸發更新。組件更新的生命周期調用順序如下:
1. static getDerivedStateFromProps() 2. shouldComponentUpdate() 3. render()4. getSnapshotBeforeUpdate() 5. componentDidUpdate()
注意:
下述方法即將過時,在新代碼中應該避免使用它們:
1. UNSAFE_componentWillUpdate() 2. UNSAFE_componentWillReceiveProps()
卸載
當組件從 DOM 中移除時會調用如下方法:
componentWillUnmount()
錯誤處理
當渲染過程,生命周期,或子組件的構造函數中拋出錯誤時,會調用如下方法:
static getDerivedStateFromError()
componentDidCatch()
常用的生命周期方法
一、render()
render()
render() 方法是 class 組件中唯一必須實現的方法。
當 render 被調用時,它會檢查 this.props 和 this.state 的變化并返回以下類型之一:
React 元素。通常通過 JSX 創建。例如,
數組或 fragments。 使得 render 方法可以返回多個元素。欲了解更多詳細信息,請參閱 fragments 文檔。
Portals。可以渲染子節點到不同的 DOM 子樹中。欲了解更多詳細信息,請參閱有關 portals 的文檔。
字符串或數值類型。它們在 DOM 中會被渲染為文本節點。
布爾類型或 null 或者 undefined。什么都不渲染。(主要用于支持 return test && 的模式,其中 test 為布爾類型。)
render() 函數應該為純函數,這意味著在不修改組件 state 的情況下,每次調用時都返回相同的結果,并且它不會直接與瀏覽器交互。
如需與瀏覽器進行交互,請在 componentDidMount() 或其他生命周期方法中執行你的操作。保持 render() 為純函數,可以使組件更容易思考。
注意
如果 shouldComponentUpdate() 返回 false,則不會調用 render()。
二、constructor()
constructor(props)
如果不初始化 state 或不進行方法綁定,則不需要為 React 組件實現構造函數。
在 React 組件掛載之前,會調用它的構造函數。在為 React.Component 子類實現構造函數時,應在其他語句之前調用 super(props)。否則,this.props 在構造函數中可能會出現未定義的 bug。
通常,在 React 中,構造函數僅用于以下兩種情況:
通過給 this.state 賦值對象來初始化內部 state。
為事件處理函數綁定實例
在 constructor() 函數中不要調用 setState() 方法。如果你的組件需要使用內部 state,請直接在構造函數中為 this.state 賦值初始 state:
constructor(props) {super(props);// 不要在這里調用 this.setState()this.state = { counter: 0 };this.handleClick = this.handleClick.bind(this);
}
只能在構造函數中直接為 this.state 賦值。如需在其他方法中賦值,你應使用 this.setState() 替代。
要避免在構造函數中引入任何副作用或訂閱。如遇到此場景,請將對應的操作放置在 componentDidMount 中。
注意
避免將 props 的值復制給 state!這是一個常見的錯誤:
constructor(props) {super(props);// 不要這樣做this.state = { color: props.color };
}
如此做毫無必要(你可以直接使用 this.props.color),同時還產生了 bug(更新 prop 中的 color 時,并不會影響 state)。
只有在你刻意忽略 prop 更新的情況下使用。此時,應將 prop 重命名為 initialColor 或 defaultColor。必要時,你可以修改它的 key,以強制“重置”其內部 state。
請參閱關于避免派生狀態的博文,以了解出現 state 依賴 props 的情況該如何處理。
三、componentDidMount()
componentDidMount()
componentDidMount() 會在組件掛載后(插入 DOM 樹中)立即調用。依賴于 DOM 節點的初始化應該放在這里。如需通過網絡請求獲取數據,此處是實例化請求的好地方。
這個方法是比較適合添加訂閱的地方。如果添加了訂閱,請不要忘記在 componentWillUnmount() 里取消訂閱
你可以在 componentDidMount() 里直接調用 setState()。它將觸發額外渲染,但此渲染會發生在瀏覽器更新屏幕之前。如此保證了即使在 render() 兩次調用的情況下,用戶也不會看到中間狀態。請謹慎使用該模式,因為它會導致性能問題。通常,你應該在 constructor() 中初始化 state。如果你的渲染依賴于 DOM 節點的大小或位置,比如實現 modals 和 tooltips 等情況下,你可以使用此方式處理
四、componentDidUpdate()
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate() 會在更新后會被立即調用。首次渲染不會執行此方法。
當組件更新后,可以在此處對 DOM 進行操作。如果你對更新前后的 props 進行了比較,也可以選擇在此處進行網絡請求。(例如,當 props 未發生變化時,則不會執行網絡請求)。
componentDidUpdate(prevProps) {// 典型用法(不要忘記比較 props):if (this.props.userID !== prevProps.userID) {this.fetchData(this.props.userID);}
}
你也可以在 componentDidUpdate() 中直接調用 setState(),但請注意它必須被包裹在一個條件語句里,正如上述的例子那樣進行處理,否則會導致死循環。它還會導致額外的重新渲染,雖然用戶不可見,但會影響組件性能。不要將 props “鏡像”給 state,請考慮直接使用 props。 欲了解更多有關內容,請參閱為什么 props 復制給 state 會產生 bug。
如果組件實現了 getSnapshotBeforeUpdate() 生命周期(不常用),則它的返回值將作為 componentDidUpdate() 的第三個參數 “snapshot” 參數傳遞。否則此參數將為 undefined。
注意
如果 shouldComponentUpdate() 返回值為 false,則不會調用 componentDidUpdate()。
五、componentWillUnmount()
componentWillUnmount()
componentWillUnmount() 會在組件卸載及銷毀之前直接調用。在此方法中執行必要的清理操作,例如,清除 timer,取消網絡請求或清除在 componentDidMount() 中創建的訂閱等。
componentWillUnmount() 中不應調用 setState(),因為該組件將永遠不會重新渲染。組件實例卸載后,將永遠不會再掛載它。
不常用的生命周期方法
本節中的生命周期方法并不太常用。它們偶爾會很方便,但是大部分情況下組件可能都不需要它們。你可以在生命周期圖譜中,選擇“顯示不常用的生命周期”復選框,即可看到下述相關方法。
本節中的生命周期方法并不太常用。它們偶爾會很方便,但是大部分情況下組件可能都不需要它們。你可以在生命周期圖譜中,選擇“顯示不常用的生命周期”復選框,即可看到下述相關方法。
一、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 時,仍可能導致組件重新渲染。
二、static getDerivedStateFromProps()
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps 會在調用 render 方法之前調用,并且在初始掛載及后續更新時都會被調用。它應返回一個對象來更新 state,如果返回 null 則不更新任何內容。
此方法適用于罕見的用例,即 state 的值在任何時候都取決于 props。例如,實現 組件可能很方便,該組件會比較當前組件與下一組件,以決定針對哪些組件進行轉場動畫。
派生狀態會導致代碼冗余,并使組件難以維護。 確保你已熟悉這些簡單的替代方案:
如果你需要執行副作用(例如,數據提取或動畫)以響應 props 中的更改,請改用 componentDidUpdate。
如果只想在 prop 更改時重新計算某些數據,請使用 memoization helper 代替。
如果你想在 prop 更改時“重置”某些 state,請考慮使組件完全受控或使用 key 使組件完全不受控 代替。
此方法無權訪問組件實例。如果你需要,可以通過提取組件 props 的純函數及 class 之外的狀態,在getDerivedStateFromProps()和其他 class 方法之間重用代碼。
請注意,不管原因是什么,都會在每次渲染前觸發此方法。這與 UNSAFE_componentWillReceiveProps 形成對比,后者僅在父組件重新渲染時觸發,而不是在內部調用 setState 時。
三、getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate() 在最近一次渲染輸出(提交到 DOM 節點)之前調用。它使得組件能在發生更改之前從 DOM 中捕獲一些信息(例如,滾動位置)。此生命周期方法的任何返回值將作為參數傳遞給 componentDidUpdate()。
此用法并不常見,但它可能出現在 UI 處理中,如需要以特殊方式處理滾動位置的聊天線程等。
應返回 snapshot 的值(或 null)。
例如:
class ScrollingList extends React.Component {constructor(props) {super(props);this.listRef = React.createRef();}getSnapshotBeforeUpdate(prevProps, prevState) {// 我們是否在 list 中添加新的 items ?// 捕獲滾動??位置以便我們稍后調整滾動位置。if (prevProps.list.length < this.props.list.length) {const list = this.listRef.current;return list.scrollHeight - list.scrollTop;}return null;}componentDidUpdate(prevProps, prevState, snapshot) {// 如果我們 snapshot 有值,說明我們剛剛添加了新的 items,// 調整滾動位置使得這些新 items 不會將舊的 items 推出視圖。//(這里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)if (snapshot !== null) {const list = this.listRef.current;list.scrollTop = list.scrollHeight - snapshot;}}render() {return (<div ref={this.listRef}>{/* ...contents... */}</div>);}
}
在上述示例中,重點是從 getSnapshotBeforeUpdate 讀取 scrollHeight 屬性,因為 “render” 階段生命周期(如 render)和 “commit” 階段生命周期(如 getSnapshotBeforeUpdate 和 componentDidUpdate)之間可能存在延遲。
四、Error boundaries
Error boundaries 是 React 組件,它會在其子組件樹中的任何位置捕獲 JavaScript 錯誤,并記錄這些錯誤,展示降級 UI 而不是崩潰的組件樹。Error boundaries 組件會捕獲在渲染期間,在生命周期方法以及其整個樹的構造函數中發生的錯誤。
如果 class 組件定義了生命周期方法 static getDerivedStateFromError() 或 componentDidCatch() 中的任何一個(或兩者),它就成為了 Error boundaries。通過生命周期更新 state 可讓組件捕獲樹中未處理的 JavaScript 錯誤并展示降級 UI。
僅使用 Error boundaries 組件來從意外異常中恢復的情況;不要將它們用于流程控制。
欲了解更多詳細信息,請參閱 React 16 中的錯誤處理。
注意
Error boundaries 僅捕獲組件樹中以下組件中的錯誤。但它本身的錯誤無法捕獲。
五、static getDerivedStateFromError()
static getDerivedStateFromError(error)
此生命周期會在后代組件拋出錯誤后被調用。 它將拋出的錯誤作為參數,并返回一個值以更新 state
class ErrorBoundary extends React.Component {constructor(props) {super(props);this.state = { hasError: false };}static getDerivedStateFromError(error) {// 更新 state 使下一次渲染可以顯降級 UIreturn { hasError: true };}render() {if (this.state.hasError) {// 你可以渲染任何自定義的降級 UIreturn <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 使下一次渲染可以顯示降級 UIreturn { hasError: true };}componentDidCatch(error, info) {// "組件堆棧" 例子:// in ComponentThatThrows (created by App)// in ErrorBoundary (created by App)// in div (created by App)// in ApplogComponentStackToMyService(info.componentStack);}render() {if (this.state.hasError) {// 你可以渲染任何自定義的降級 UIreturn <h1>Something went wrong.</h1>;}return this.props.children;}
}
React 的開發和生產構建版本在 componentDidCatch() 的方式上有輕微差別。
在開發模式下,錯誤會冒泡至 window,這意味著任何 window.onerror 或 window.addEventListener(‘error’, callback) 會中斷這些已經被 componentDidCatch() 捕獲的錯誤。
相反,在生產模式下,錯誤不會冒泡,這意味著任何根錯誤處理器只會接受那些沒有顯式地被 componentDidCatch() 捕獲的錯誤。
注意
如果發生錯誤,你可以通過調用 setState 使用 componentDidCatch() 渲染降級 UI,但在未來的版本中將不推薦這樣做。 可以使用靜態 getDerivedStateFromError() 來處理降級渲染。
其他 API
不同于上述生命周期方法(React 主動調用),以下方法是你可以在組件中調用的方法。
只有兩個方法:setState() 和 forceUpdate()。
setState()
setState(updater[, callback])
setState() 將對組件 state 的更改排入隊列,并通知 React 需要使用更新后的 state 重新渲染此組件及其子組件。這是用于更新用戶界面以響應事件處理器和處理服務器數據的主要方式
將 setState() 視為請求而不是立即更新組件的命令。為了更好的感知性能,React 會延遲調用它,然后通過一次傳遞更新多個組件。在罕見的情況下,你需要強制 DOM 更新同步應用,你可以使用 flushSync 來包裝它,但這可能會損害性能。
setState() 并不總是立即更新組件。它會批量推遲更新。這使得在調用 setState() 后立即讀取 this.state 成為了隱患。為了消除隱患,請使用 componentDidUpdate 或者 setState 的回調函數(setState(updater, callback)),這兩種方式都可以保證在應用更新后觸發。如需基于之前的 state 來設置當前的 state,請閱讀下述關于參數 updater 的內容。
除非 shouldComponentUpdate() 返回 false,否則 setState() 將始終執行重新渲染操作。如果可變對象被使用,且無法在 shouldComponentUpdate() 中實現條件渲染,那么僅在新舊狀態不一時調用 setState()可以避免不必要的重新渲染
參數一為帶有形式參數的 updater 函數:
(state, props) => stateChange
state 是對應用變化時組件狀態的引用。當然,它不應直接被修改。你應該使用基于 state 和 props 構建的新對象來表示變化。例如,假設我們想根據 props.step 來增加 state:
this.setState((state, props) => {return {counter: state.counter + props.step};
});
updater 函數中接收的 state 和 props 都保證為最新。updater 的返回值會與 state 進行淺合并。
setState() 的第二個參數為可選的回調函數,它將在 setState 完成合并并重新渲染組件后執行。通常,我們建議使用 componentDidUpdate() 來代替此方式。
setState() 的第一個參數除了接受函數外,還可以接受對象類型:
setState(stateChange[, callback])
stateChange 會將傳入的對象淺層合并到新的 state 中,例如,調整購物車商品數:
this.setState({quantity: 2})
這種形式的 setState() 也是異步的,并且在同一周期內會對多個 setState 進行批處理。例如,如果在同一周期內多次設置商品數量增加,則相當于:
Object.assign(previousState,{quantity: state.quantity + 1},{quantity: state.quantity + 1},...
)
后調用的 setState() 將覆蓋同一周期內先調用 setState 的值,因此商品數僅增加一次。如果后續狀態取決于當前狀態,我們建議使用 updater 函數的形式代替:
this.setState((state) => {return {quantity: state.quantity + 1};
});
有關更多詳細信息,請參閱:
State 和生命周期指南
深入學習:何時以及為什么 setState() 會批量執行?
深入:為什么不直接更新 this.state?
forceUpdate()
component.forceUpdate(callback)
默認情況下,當組件的 state 或 props 發生變化時,組件將重新渲染。如果 render() 方法依賴于其他數據,則可以調用 forceUpdate() 強制讓組件重新渲染。
調用 forceUpdate() 將致使組件調用 render() 方法,此操作會跳過該組件的 shouldComponentUpdate()。但其子組件會觸發正常的生命周期方法,包括 shouldComponentUpdate() 方法。如果標記發生變化,React 仍將只更新 DOM。
通常你應該避免使用 forceUpdate(),盡量在 render() 中使用 this.props 和 this.state。
Class 屬性
defaultProps
defaultProps 可以為 Class 組件添加默認 props。這一般用于 props 未賦值,但又不能為 null 的情況。例如:
class CustomButton extends React.Component {// ...
}CustomButton.defaultProps = {color: 'blue'
};
如果未提供 props.color,則默認設置為 ‘blue’
render() {return <CustomButton /> ; // props.color 將設置為 'blue'}
如果 props.color 被設置為 null,則它將保持為 null
render() {return <CustomButton color={null} /> ; // props.color 將保持是 null}
displayName
displayName 字符串多用于調試消息。通常,你不需要設置它,因為它可以根據函數組件或 class 組件的名稱推斷出來。如果調試時需要顯示不同的名稱或創建高階組件,請參閱使用 displayname 輕松進行調試了解更多。
實例屬性
props
this.props 包括被該組件調用者定義的 props。欲了解 props 的詳細介紹,請參閱組件 & Props。
需特別注意,this.props.children 是一個特殊的 prop,通常由 JSX 表達式中的子組件組成,而非組件本身定義。
state
組件中的 state 包含了隨時可能發生變化的數據。state 由用戶自定義,它是一個普通 JavaScript 對象。
如果某些值未用于渲染或數據流(例如,計時器 ID),則不必將其設置為 state。此類值可以在組件實例上定義。
欲了解關于 state 的更多信息,請參閱 State & 生命周期。
永遠不要直接改變 this.state,因為后續調用的 setState() 可能會替換掉你的改變。請把 this.state 看作是不可變的。