概覽
我們編寫的大部分 React 的組件都為class組件,掌握class組件的原理和應(yīng)用能幫助我們寫出更好的代碼,本文主要會(huì)從生命周期、state狀態(tài)這兩個(gè)方面來介紹ReactClass組件斯撮,閱讀本文能讓你快速掌握ReactClass組件的要點(diǎn)。
生命周期
我會(huì)先向大家講解react16.3以后的生命周期扶叉,然后解釋下部分老舊的生命周期鉤子被廢棄/不推薦使用的原因勿锅。
執(zhí)行順序
以上是react官方給出的生命周期圖譜,大致分為3個(gè)階段辜梳,Mounting粱甫、Updating、Unmounting作瞄,下面是每個(gè)階段的執(zhí)行順序茶宵。
Mounting
Mounting階段的執(zhí)行順序依次為:
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
Updating
Updating階段的執(zhí)行順序依次為:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
Unmounting
Unmounting階段只會(huì)調(diào)用一個(gè)函數(shù):
- componentWillUnmount()
Error
除了以上3個(gè)階段,React在錯(cuò)誤發(fā)生時(shí)還提供了兩個(gè)鉤子用來做一些錯(cuò)誤降級(jí)/上報(bào)處理:
- static getDerivedStateFromError()
- componentDidCatch()
以上兩個(gè)方法的區(qū)別在于宗挥,static getDerivedStateFromError()方法用于降級(jí)乌庶,而componentDidCatch()用于一些錯(cuò)誤上報(bào)。
鉤子詳解
下面會(huì)詳細(xì)解釋上面提到的每一個(gè)鉤子
constructor(props)
constructor(props)為class組件的構(gòu)造函數(shù)契耿,一般有兩個(gè)作用:
- 給state對(duì)象初始化賦值
- 處理方法瞒大,比如防抖、節(jié)流搪桂、bind等
constructor(props) {
super(props)
this.state = {id: 1}
this.onSelectUser = throttle(this.onSelectUser, 500)
}
以上代碼首先給state賦了初始值透敌,然后將onSelectUser方法用節(jié)流函數(shù)處理后得到了一個(gè)新方法盯滚。
static getDerivedStateFromProps(props, state)
該函數(shù)有兩個(gè)要點(diǎn):
- 每次render前都會(huì)調(diào)用該方法,也就是說無論是初始化階段還是更新階段都會(huì)調(diào)用static getDerivedStateFromProps(props, state)酗电。
- 該函數(shù)的作用很簡單:return一個(gè)對(duì)象來更新state魄藕,如果return null則不更新任何內(nèi)容。
static getDerivedStateFromProps(props, state) {
if (props.id !== state.id) {
return {
id: props.id
}
}
if (typeof props.expandedkey !== 'undefined') {
return {
expandedkey: props.expandedkey
}
}
return null
}
以上代碼為一個(gè)示例撵术,需要注意的是背率,該方法無法訪問組件實(shí)例,所以你僅僅能根據(jù)props和state的值經(jīng)過一些條件比較嫩与,最后return一個(gè)對(duì)象更新state的值寝姿。
shouldComponentUpdate(nextProps, nextState)
該函數(shù)的唯一作用為return一個(gè)布爾值來決定是否重新渲染組件,true為更新划滋,false為不更新
shouldComponentUpdate(nextProps, nextState) {
if (this.props.text !== nextProps.text) {
return true
}
if (this.state.weight !== nextState.weight) {
return true
}
return false
}
以上示例中可以看到饵筑,在shouldComponentUpdate方法中,我們能獲得nextProps古毛、nextState從而與this.props翻翩、this.state進(jìn)行比較,最后決定組件的更新與否稻薇。
render()
該函數(shù)最為常用嫂冻,我們需要知道render函數(shù)究竟怎么渲染為dom節(jié)點(diǎn)。
render函數(shù)會(huì)返回以下類型之一:
- React元素:
<div/>
會(huì)被渲染為dom節(jié)點(diǎn)塞椎,<MyComponent/>
會(huì)被渲染為自定義組件桨仿,這兩種都為React元素,react根據(jù)第一個(gè)字母的大小寫來決定時(shí)是dom節(jié)點(diǎn)還是自定義組件案狠。 - 數(shù)組或 fragments
- Portals
- 字符串或數(shù)值類型
- 布爾類型或 null
在render函數(shù)中服傍,react會(huì)先判斷元素的類型,然后根據(jù)不行的類型執(zhí)行不同的解析方法骂铁。
componentDidMount()
一般人對(duì)該方法都比較熟悉吹零,componentDidMount的調(diào)用時(shí)機(jī)在組件第一次render之后,一般可以在此方法中使用ajax請(qǐng)求獲取數(shù)據(jù)后使用setState再次更新狀態(tài)拉庵。
componentDidMount() {
this.props.getCode({
id: 'clue'
}).then((res) => {
if(res.code){
setState({
code: res.code
})
}
}).catch(err){
console.log(err)
}
}
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate會(huì)在更新階段的render后被調(diào)用
componentDidUpdate(prevProps) {
if (prevProps.refresh !== this.props.refresh) {
this.init()
this.ajax()
}
}
如上示例灿椅,一般在componentDidUpdate中會(huì)對(duì)prevProps、prevState钞支、props茫蛹、state進(jìn)行一些比較,然后執(zhí)行ajax請(qǐng)求或其他函數(shù)烁挟。
getSnapshotBeforeUpdate(prevProps, prevState)
- 僅在更新階段的render函數(shù)之后被調(diào)用
- 返回一個(gè)值婴洼,該值會(huì)作為componentDidUpdate方法的第三個(gè)參數(shù)
getSnapshotBeforeUpdate(prevProps, prevState) {
return this.testNode.scrollHeight
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log(snapshot)
}
render(){
return (
<div ref = { node => ( this.testNode = node)}> </div>
)
}
如上示例,在getSnapshotBeforeUpdate方法中適合獲取dom的一些位置屬性然后return撼嗓,并在componentDidUpdate方法中作為第三個(gè)參數(shù)傳入柬采。
componentWillUnmount()
componentWillUnmount() 會(huì)在組件卸載及銷毀之前直接調(diào)用欢唾,一般在此方法中清除一些內(nèi)存,比如清除定時(shí)器警没、中斷網(wǎng)絡(luò)請(qǐng)求等匈辱。
componentWillUnmount(){
// 清除定時(shí)器
this.timer = null
}
componentDidCatch(error, info)
該方法的兩個(gè)參數(shù)分別為:
- error:拋出的錯(cuò)誤
- info:有關(guān)組件引發(fā)錯(cuò)誤的棧信息
componentDidCatch(error, info) {
log(info.componentStack);
}
如上示例,一般在componentDidCatch方法中上報(bào)一些錯(cuò)誤至監(jiān)控平臺(tái)杀迹,有時(shí)也會(huì)在最外層React組件的componentDidCatch方法中對(duì)所有error錯(cuò)誤進(jìn)行捕獲。
static getDerivedStateFromError(error)
該方法會(huì)在后代組件拋出錯(cuò)誤后被調(diào)用,在錯(cuò)誤發(fā)生時(shí),會(huì)返回一個(gè)對(duì)象來更新state耙替,以達(dá)到降級(jí)的目的翼抠。以上兩個(gè)方法的區(qū)別在于,getDerivedStateFromError方法用于降級(jí)邀窃,而componentDidCatch用于一些錯(cuò)誤上報(bào)。
被棄用的鉤子
以下生命周期都是要被廢除掉的,暫時(shí)可以不用關(guān)注疮茄。
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
官方給出的解釋為:這些生命周期方法經(jīng)常被誤解和濫用。
以componentWillUpdate為例:在異步模式下使用 componentWillUpdate 都是不安全的根暑,想象一下在componentWillUpdate中調(diào)用外部回調(diào)力试,改變外部狀態(tài)以后又可能觸發(fā)props改變,從而又會(huì)調(diào)用componentWillUpdate排嫌,從而陷入死循環(huán)畸裳。而componentDidUpdate方法能保證每次更新只調(diào)用一次,使用上更加安全淳地。
組件狀態(tài)
梳理生命周期能幫助我們熟練書寫React代碼怖糊,而理解組件狀態(tài)如何更新,則幫助我們?cè)趹?yīng)對(duì)疑難問題時(shí)更加輕松颇象。
在React中伍伤,我們使用setState(updater, [callback])更新組件狀態(tài),因?yàn)閟etState并不立刻執(zhí)行遣钳,所以在setState之后我們無法立刻獲得組件的最新state扰魂。
setState({id:1}, () => {
console.log(this.state.id)
})
如上代碼所示,React提供了一種方式:即在setState的回調(diào)函數(shù)中獲得最新的state(在componentDidUpdate中也能獲取到最新state)耍贾。
既然如此阅爽,setState為什么不做成同步呢,做成異步很重要的一個(gè)原因是:性能優(yōu)化荐开,合并多個(gè)setState付翁,避免多次渲染。
React事件中的setState會(huì)被存放于一個(gè)pending隊(duì)列中晃听,當(dāng)同步代碼都執(zhí)行完后百侧,才會(huì)批量更新state砰识。由于setState的異步執(zhí)行是基于React事件,所以像原生js事件以及setTimeout定時(shí)器中的setState方法就不會(huì)走異步邏輯佣渴,在這些情況下辫狼,我們可以同步獲取最新state。
this.state = {
id: 1
}
componentDidMount() {
this.setState({id: this.state.id + 1 });
console.log(this.state.id);
this.setState({id: this.state.id + 1 });
console.log(this.state.id);
setTimeout(() => {
console.log(this.state.id);
this.setState({id: this.state.id + 1 });
console.log(this.state.id);
}, 0);
}
以上代碼的執(zhí)行結(jié)果為0 0 1 2
辛润,我們簡要分析一下:
因?yàn)閟etState為異步膨处,一開始的兩次setState都進(jìn)入了pending隊(duì)列,所以打印出來都是0砂竖,setTimeout為宏任務(wù)真椿,setTimeout中的state是setState合并執(zhí)行后的結(jié)果,此時(shí)id為1乎澄;而為什么在最后一次setState后打印出了最新的id(2)呢突硝?這是因?yàn)閟etState的異步依賴于React事件,此時(shí)setState在原生js定時(shí)器中執(zhí)行置济,無法實(shí)現(xiàn)異步setState解恰,所以能同步獲取到最新的state。
除了setState以外浙于,React還提供了一個(gè)forceUpdate()函數(shù)來讓組件強(qiáng)制渲染护盈,但是我們?cè)诰帉懘a時(shí)應(yīng)該盡量不使用該方法。
總結(jié)
- 在日常開發(fā)中路媚,可以經(jīng)郴魄恚回顧以上Mounting、Updating整慎、Unmounting的生命周期脏款。
- 遇到舊代碼中的被廢棄的生命周期鉤子時(shí)可查閱相關(guān)使用,并嘗試將其升級(jí)裤园。
- 使用setState時(shí)需要注意撤师,在原生js事件、setTimeOut等操作中的setState是同步更新狀態(tài)的拧揽。