發(fā)現(xiàn)好像有些沒(méi)有過(guò)的生命周期函數(shù),還沒(méi)完全弄清楚...
一、組件的生命周期
組件的生命周期,主要分為 Mounting(掛載)、Updating(更新)、Unmounting(卸載)三個(gè)階段。
Mounting
當(dāng)組件示例被創(chuàng)建并插入 DOM 中時(shí),其生命周期調(diào)用順序如下:
以下生命周期方法即將過(guò)時(shí),在新代碼中應(yīng)該避免使用它們:
UNSAFE_componentWillMount()
。
Updating
當(dāng)組件的 props 或 state 發(fā)生變化時(shí)會(huì)觸發(fā)更新。組件更新的生命周期調(diào)用順序如下:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
請(qǐng)注意,以下方法即將過(guò)時(shí),在新代碼中應(yīng)該避免使用它們:
UNSAFE_componentWillUpdate()
、UNSAFE_componentWillReceiveProps()
Unmounting
當(dāng)組件從 DOM 中移除時(shí)會(huì)調(diào)用如下方法:
Error Handling(錯(cuò)誤處理)
當(dāng)渲染過(guò)程,生命周期,或子組件的構(gòu)造函數(shù)中拋出錯(cuò)誤時(shí),會(huì)調(diào)用如下方法:
二、Mounting(掛載)
2.1 constructor
constructor(props)
如果不初始化 state 或不進(jìn)行方法綁定,則不需要為 React 組件實(shí)現(xiàn)構(gòu)造函數(shù)。
在 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。
一般需重寫(xiě)構(gòu)造函數(shù),只做兩件事:
- 初始化組件內(nèi)部 state,即
this.state = { ... }
。 - 為時(shí)間處理函數(shù)綁定實(shí)例,如:
this.handleClick = this.handleClick.bind(this)
否則無(wú)需為 React 組件實(shí)現(xiàn)構(gòu)造函數(shù)。
2.2 static getDerivedStateFromProps(不常用)
static getDerivedStateFromProps(props, state)
需要注意的是,此方法無(wú)法訪問(wèn)組件實(shí)例,即不能使用
this
。
getDerivedStateFromState
會(huì)在調(diào)用 render
方法之前調(diào)用,并且在初始掛載及后續(xù)更新時(shí)都會(huì)被調(diào)用。它應(yīng)返回一個(gè) 對(duì)象
來(lái)更新 state,如果返回 null
則不更新任何內(nèi)容。
與 componentWillReceiveProps
不同的是,getDerivedStateFromProps
不管原因是什么,都會(huì)在每次渲染前觸發(fā)此方法。而 componentWillReceiveProps
僅在父組件重新渲染時(shí)觸發(fā),而不是在內(nèi)部調(diào)用 setState
時(shí)。
請(qǐng)避免使用派生 state。
2.2.1 UNSAFE_componentWillReceiveProps
此生命周期之前名為
componentWillReceiveProps
。該名稱將繼續(xù)使用至 React 17。在 React 16.3 之后,它的替代者是getDerivedStateFromProps
。
UNSAFE_componentWillReceiveProps()
會(huì)在已掛載的組件接收新的 props
之前被調(diào)用。如果你需要更新?tīng)顟B(tài)以響應(yīng) prop
更新(例如,重置它),你可以比較 this.props
和 nextProps
并在此方法中使用 this.setState()
執(zhí)行 state
轉(zhuǎn)換。
請(qǐng)注意,如果父組件導(dǎo)致組件重新渲染,即使沒(méi)有 props
沒(méi)有更改,也會(huì)調(diào)用此方法。如果只是想處理更改,請(qǐng)確保進(jìn)行當(dāng)前值與變更值的比較。
在掛載過(guò)程,React 不會(huì)針對(duì)初始 props
調(diào)用 UNSAFE_componentWillReceiveProps()
。組件只會(huì)在組件的 props
更新時(shí)調(diào)用此方法。調(diào)用 this.setState()
通常不會(huì)觸發(fā) UNSAFE_componentWillReceiveProps()
。
2.3 UNSAFE_componentWillMount
該生命周期之前名為
componentWillMount
,舊名稱仍可使用至 React 17.
UNSAFE_componentWillMount()
在 constructor
之后,render
之前被調(diào)用,因此在此方法中同步調(diào)用 setState()
不會(huì)觸發(fā)額外渲染。通常,我們建議使用 constructor()
來(lái)初始化 state。
避免在此方法引入任何副作用或訂閱,請(qǐng)放置在
componentDidMount()
。
此方法是服務(wù)端渲染唯一會(huì)調(diào)用的生命周期函數(shù)。
2.4 render
render()
方法是 class 組件中唯一必須實(shí)現(xiàn)的方法。
當(dāng) render
被調(diào)用,它會(huì)檢測(cè) this.props
和 this.state
的變化并返回以下類型之一:
-
React 元素:通常通過(guò) JSX 創(chuàng)建。例如,
<div />
會(huì)被 React 渲染為 DOM 節(jié)點(diǎn),<MyComponent />
會(huì)被 React 渲染為自定義組件,無(wú)論是<div />
還是<MyComponent />
均為 React 元素。 - 數(shù)組或 fragments: 使得
render
方法可以返回多個(gè)元素。欲了解更多詳細(xì)信息,請(qǐng)參閱 fragments 文檔。 - Portals:可以渲染子節(jié)點(diǎn)到不同的 DOM 子樹(shù)中。欲了解更多詳細(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ù),可以使組件更容易思考。
需要注意都是,
shouleComponentUpdate()
返回false
,則不會(huì)調(diào)用render()
。
2.5 componentDidMount
componentDidMount()
componentDidMount()
會(huì)在組件掛載后(插入 DOM 樹(shù)中)立即調(diào)用。依賴于 DOM 節(jié)點(diǎn)的初始化應(yīng)該放在這里。如需通過(guò)網(wǎng)絡(luò)請(qǐng)求獲取數(shù)據(jù),此處是實(shí)例化請(qǐng)求的好地方。
這個(gè)地方是比較合適添加訂閱。如果添加了訂閱,請(qǐng)不要忘記在 componentWillUnmount()
里取消訂閱。
你可以在
componentDidMount()
里直接調(diào)用setState()
。它將會(huì)觸發(fā)額外渲染,但此渲染會(huì)發(fā)生在瀏覽器更新屏幕之前。如此保證了即使在 render() 兩次調(diào)用的情況下,用戶也不會(huì)看到中間狀態(tài)。請(qǐng)謹(jǐn)慎使用該模式,因?yàn)樗鼤?huì)導(dǎo)致性能問(wèn)題。通常,你應(yīng)該在constructor()
中初始化 state。如果你的渲染依賴于 DOM 節(jié)點(diǎn)的大小或位置,比如實(shí)現(xiàn) modals 或 tooltips 等情況下,你可以使用此方式處理。
三、Updating(更新)
3.1 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)化的方式而存在。不要企圖依靠此方法來(lái)“阻止”渲染,因?yàn)檫@可能會(huì)產(chǎn)生 bug。你應(yīng)該考慮使用內(nèi)置的 PureComponent 組件,而不是手動(dòng)編寫(xiě) shouldComponentUpdate()
。PureComponent 會(huì)對(duì) props
和 state
進(jìn)行淺層比較,并減少了跳過(guò)必要更新的可能性。
如果你一定要手寫(xiě)編寫(xiě)此函數(shù),可以將 this.props
與 nextProps
以及 this.state
與 nextState
進(jìn)行比較,并返回 false
以告知 React 可以跳過(guò)更新。請(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.2 UNSAFE_componentWillUpdate(不常用)
UNSAFE_componentWillUpdate(nextProps, nextState)
此生命周期之前名為
componentWillUpdate
。該名稱將繼續(xù)使用至 React 17。
當(dāng)組件收到新的 props
或 state
時(shí),會(huì)在渲染之前調(diào)用 UNSAFE_componentWillUpdate()
。使用此作為更新發(fā)生之前執(zhí)行準(zhǔn)備更新的幾乎。初始渲染不會(huì)調(diào)用此方法。
注意,你不能此方法中調(diào)用 setState()
;在 UNSAFE_componentWillUpdate()
返回之前,你不應(yīng)該執(zhí)行任何其他操作(例如,dispatch Redux 的 action)觸發(fā)對(duì) React 組件的更新。
通常,此方法可以替換為 componentDidUpdate()
。如果你在此方法中讀取 DOM 信息(例如,為了保存滾動(dòng)違章),則可以將此邏輯移至 getSnapShotBeforeUpdate()
中。
如果
shouldComponentUpdate()
返回false
,則不會(huì)調(diào)用UNSAFE_componentWillUpdate()
。
3.3 componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate()
會(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(prevProps) {
// 典型用法(不要忘記比較 props)
// 若直接 setState() 會(huì)導(dǎo)致死循環(huán)。
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID)
}
}
你也可以在 componentDidUpdate()
中直接調(diào)用 setState()
,但請(qǐng)注意它必須被包裹在一個(gè)條件語(yǔ)句里,正如上述的例子那樣進(jìn)行處理,否則會(huì)導(dǎo)致死循環(huán)。它還會(huì)導(dǎo)致額外的重新渲染,雖然用戶不可見(jiàn),但會(huì)影響組件性能。不要將 props
“鏡像”給 state,請(qǐng)考慮直接使用 props
。
如果組件實(shí)現(xiàn)了 getSnapshotBeforeUpdate()
生命周期(不常用),則它的返回值將作為 componentDidUpdate()
的第三個(gè)參數(shù) snapshot
傳遞。否則此參數(shù)將為 undefined
。
需要注意的是,
shouldComponentUpdate()
返回值為false
,則不會(huì)調(diào)用componentDidUpdate()
。
四、Unmounting(卸載)
4.1 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ì)再掛載它。
需要注意的是,我們常說(shuō)的組件銷毀,只是組件從頁(yè)面中刪除而已。而并不意味著它真正被銷毀,被 GC 回收。我們所寫(xiě)的 JSX 形式的組件,都會(huì)被 Babel 轉(zhuǎn)化成
React.createElement()
形式的JS 對(duì)象(沒(méi)錯(cuò),React 組件本質(zhì)上就是一個(gè) JavaScript 對(duì)象)。因此只有它不再被引用,垃圾回收機(jī)制才會(huì)回收掉,這才算真正銷毀。這也是為什么要做componentWillUnmount
清除副作用的原因。
五、其他
5.1 defaultProps
defaultProps
可以為 Class 組件添加默認(rèn) props
。這一般用于 props
未賦值,但又不能為 null
的情況。例如:
class CustomButton extends Component {
// ...
}
CustomButton.defaultProps = {
type: 'primary'
}
利用 ES6 的 class 靜態(tài)語(yǔ)法
class CustomButton extends Component {
static defaultProps = {
type: 'primary'
}
// ...
}
5.2 一些問(wèn)題
React 16 之后采用了 Fiber 架構(gòu),只有 componentDidMount
生命周期函數(shù)是確定被執(zhí)行一次的,類似 ComponentWillMount
的生命周期鉤子都有可能執(zhí)行多次,所以不加以在這些生命周期中做有副作用的操作,比如請(qǐng)求數(shù)據(jù)之類。
3.3 componentDidCatch()
未完待續(xù)...
五、逐步遷移路徑
React 遵循語(yǔ)義版本控制,因此這種變化將是逐步的。我們目前的計(jì)劃是:
-
16.3:為不安全的生命周期引入別名,
UNSAFE_componentWillMount
、UNSAFE_componentWillReceiveProps
和UNSAFE_componentWillUpdate
。(舊的生命周期名稱和新的別名都可以在此版本中使用。) -
未來(lái) 16.x 版本:為
componentWillMount
、componentWillReceiveProps
和componentWillUpdate
啟用廢棄告警。(舊的生命周期名稱和新的別名都將在這個(gè)版本中工作,但是舊的名稱在開(kāi)發(fā)模式下會(huì)產(chǎn)生一個(gè)警告。) -
17.0:刪除
componentWillMount
、componentWillReceiveProps
和componentWillUpdate
。(在此版本之后,只有新的UNSAFE_
生命周期名稱可以使用。)
這里的 “unsafe” 不是指安全性,而是表示使用這些生命周期的代碼在 React 的未來(lái)版本中更有可能出現(xiàn) bug,尤其是在啟用異步渲染之后。
六、示例
由于使用新 API(如
getDerivedStateFromProps
、getSnapshotBeforeUpdate
)時(shí),React 不會(huì)調(diào)用組件的 “unsafe” 生命周期(如UNSAFE_componentWillMount
、UNSAFE_componentWillReceiveProps
、UNSAFE_componentWillUpdate
),因此暫時(shí)先注釋掉新 API。同時(shí)使用,會(huì)出現(xiàn)類似如下警告??:
Warning: Unsafe legacy lifecycles will not be called for components using new component APIs.
LifeCycle uses getDerivedStateFromProps() but also contains the following legacy lifecycles:
UNSAFE_componentWillMount
The above lifecycles should be removed. Learn more about this warning here:
https://fb.me/react-unsafe-component-lifecycles
示例基于 React 16.12.0 版本。
import React, { Component } from 'react'
export default class LifeCycle extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
this.setCount = this.setCount.bind(this)
console.log('---> constructor')
}
static getDerivedStateFromProps(prevProps, prevState) {
console.log('---> getDerivedStateFromProps')
return null
}
// 不會(huì)為使用新組件 API 的組件調(diào)用不安全的遺留生命周期。
// Warning: Unsafe legacy lifecycles will not be called for components using new component APIs.
// LifeCycle uses getDerivedStateFromProps() but also contains the following legacy lifecycles: UNSAFE_componentWillMount
// UNSAFE_componentWillMount() {
// console.log('---> UNSAFE_component')
// }
componentDidMount() {
console.log('---> componentDidMount')
}
setCount() {
this.setState({ count: this.state.count + 1 })
}
render() {
console.log('---> render')
return (
<div>
<div>Count: {this.state.count}</div>
<button onClick={this.setCount}>Add</button>
</div>
)
}
}
當(dāng)首次加載加載時(shí),分別觸發(fā)了以下生命周期,打印結(jié)果:
---> constructor
---> UNSAFE_componentWillMount
---> render
---> componentDidMount
當(dāng)我們點(diǎn)擊按鈕通過(guò) setState
更新 count
時(shí),會(huì)觸發(fā)以下動(dòng)作:
---> shouldComponentUpdate
---> UNSAFE_componentWillUpdate
---> render
---> componentDidUpdate
未完待續(xù)...