發(fā)現(xiàn)好像有些沒有過的生命周期函數(shù)梗夸,還沒完全弄清楚...
一反症、組件的生命周期
組件的生命周期铅碍,主要分為 Mounting(掛載)胞谈、Updating(更新)烦绳、Unmounting(卸載)三個(gè)階段径密。
Mounting
當(dāng)組件示例被創(chuàng)建并插入 DOM 中時(shí)享扔,其生命周期調(diào)用順序如下:
以下生命周期方法即將過時(shí),在新代碼中應(yīng)該避免使用它們:
UNSAFE_componentWillMount()
奋单。
Updating
當(dāng)組件的 props 或 state 發(fā)生變化時(shí)會(huì)觸發(fā)更新览濒。組件更新的生命周期調(diào)用順序如下:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
請(qǐng)注意应又,以下方法即將過時(shí)株扛,在新代碼中應(yīng)該避免使用它們:
UNSAFE_componentWillUpdate()
、UNSAFE_componentWillReceiveProps()
Unmounting
當(dāng)組件從 DOM 中移除時(shí)會(huì)調(diào)用如下方法:
Error Handling(錯(cuò)誤處理)
當(dāng)渲染過程盆繁,生命周期油昂,或子組件的構(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)該在其他語句之前調(diào)用 super(props)
辅辩。否則玫锋,this.props
在構(gòu)造函數(shù)中可能會(huì)出現(xiàn)未定義的 bug撩鹿。
一般需重寫構(gòu)造函數(shù)节沦,只做兩件事:
- 初始化組件內(nèi)部 state甫贯,即
this.state = { ... }
获搏。 - 為時(shí)間處理函數(shù)綁定實(shí)例常熙,如:
this.handleClick = this.handleClick.bind(this)
否則無需為 React 組件實(shí)現(xiàn)構(gòu)造函數(shù)裸卫。
2.2 static getDerivedStateFromProps(不常用)
static getDerivedStateFromProps(props, state)
需要注意的是墓贿,此方法無法訪問組件實(shí)例,即不能使用
this
穴吹。
getDerivedStateFromState
會(huì)在調(diào)用 render
方法之前調(diào)用啥容,并且在初始掛載及后續(xù)更新時(shí)都會(huì)被調(diào)用咪惠。它應(yīng)返回一個(gè) 對(duì)象
來更新 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ài)以響應(yīng) prop
更新(例如篓吁,重置它)杖剪,你可以比較 this.props
和 nextProps
并在此方法中使用 this.setState()
執(zhí)行 state
轉(zhuǎn)換。
請(qǐng)注意次兆,如果父組件導(dǎo)致組件重新渲染漓库,即使沒有 props
沒有更改,也會(huì)調(diào)用此方法园蝠。如果只是想處理更改渺蒿,請(qǐng)確保進(jìn)行當(dāng)前值與變更值的比較。
在掛載過程彪薛,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()
來初始化 state兵睛。
避免在此方法引入任何副作用或訂閱假颇,請(qǐng)放置在
componentDidMount()
。
此方法是服務(wù)端渲染唯一會(huì)調(diào)用的生命周期函數(shù)。
2.4 render
render()
方法是 class 組件中唯一必須實(shí)現(xiàn)的方法已卸。
當(dāng) render
被調(diào)用奥吩,它會(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ù)值類型:它們在 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 樹中)立即調(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()
翅敌。它將會(huì)觸發(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 等情況下,你可以使用此方式處理鸥滨。
三嗦哆、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)化的方式而存在。不要企圖依靠此方法來“阻止”渲染嗡官,因?yàn)檫@可能會(huì)產(chǎn)生 bug箭窜。你應(yīng)該考慮使用內(nèi)置的 PureComponent 組件,而不是手動(dòng)編寫 shouldComponentUpdate()
衍腥。PureComponent 會(huì)對(duì) props
和 state
進(jìn)行淺層比較磺樱,并減少了跳過必要更新的可能性纳猫。
如果你一定要手寫編寫此函數(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.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è)條件語句里攻柠,正如上述的例子那樣進(jìn)行處理球订,否則會(huì)導(dǎo)致死循環(huán)。它還會(huì)導(dǎo)致額外的重新渲染瑰钮,雖然用戶不可見冒滩,但會(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ì)再掛載它饭寺。
需要注意的是,我們常說的組件銷毀叫挟,只是組件從頁面中刪除而已艰匙。而并不意味著它真正被銷毀,被 GC 回收抹恳。我們所寫的 JSX 形式的組件员凝,都會(huì)被 Babel 轉(zhuǎn)化成
React.createElement()
形式的JS 對(duì)象(沒錯(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)語法
class CustomButton extends Component {
static defaultProps = {
type: 'primary'
}
// ...
}
5.2 一些問題
React 16 之后采用了 Fiber 架構(gòu),只有 componentDidMount
生命周期函數(shù)是確定被執(zhí)行一次的祟敛,類似 ComponentWillMount
的生命周期鉤子都有可能執(zhí)行多次疤坝,所以不加以在這些生命周期中做有副作用的操作,比如請(qǐng)求數(shù)據(jù)之類馆铁。
3.3 componentDidCatch()
未完待續(xù)...
五跑揉、逐步遷移路徑
React 遵循語義版本控制,因此這種變化將是逐步的埠巨。我們目前的計(jì)劃是:
-
16.3:為不安全的生命周期引入別名历谍,
UNSAFE_componentWillMount
、UNSAFE_componentWillReceiveProps
和UNSAFE_componentWillUpdate
乖订。(舊的生命周期名稱和新的別名都可以在此版本中使用扮饶。) -
未來 16.x 版本:為
componentWillMount
、componentWillReceiveProps
和componentWillUpdate
啟用廢棄告警乍构。(舊的生命周期名稱和新的別名都將在這個(gè)版本中工作甜无,但是舊的名稱在開發(fā)模式下會(huì)產(chǎn)生一個(gè)警告。) -
17.0:刪除
componentWillMount
哥遮、componentWillReceiveProps
和componentWillUpdate
岂丘。(在此版本之后,只有新的UNSAFE_
生命周期名稱可以使用眠饮。)
這里的 “unsafe” 不是指安全性奥帘,而是表示使用這些生命周期的代碼在 React 的未來版本中更有可能出現(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)擊按鈕通過 setState
更新 count
時(shí)璃诀,會(huì)觸發(fā)以下動(dòng)作:
---> shouldComponentUpdate
---> UNSAFE_componentWillUpdate
---> render
---> componentDidUpdate
未完待續(xù)...