關於react生命週期的文章,網上一大堆,本人也看了許多,但是覺得大部分人寫的都是照搬其它人的沒有自己獨到的見解,所以決定根據本人的實戰經驗和個人理解再寫一篇React生命週期的文章,由於React目前已更新到16.4版本,所以重點講解React v16.4變化的生命週期,之前的生命週期函式會一帶而過
先總體看下React16的生命週期圖
React16廢棄的三個生命週期函式
componentWillMountcomponentWillReceivePropscomponentWillUpdate
注:目前在16版本中
componentWillMount,componentWillReceiveProps,componentWillUpdate並未完全刪除這三個生命週期函式,而且新增了UNSAFE_componentWillMount,UNSAFE_componentWillReceiveProps,UNSAFE_componentWillUpdate三個函式,官方計劃在17版本完全刪除這三個函式,只保留UNSAVE_字首的三個函式,目的是為了向下相容,但是對於開發者而言應該儘量避免使用他們,而是使用新增的生命週期函式替代它們
取而代之的是兩個新的生命週期函式
- static getDerivedStateFromProps
- getSnapshotBeforeUpdate
我們將React的生命週期分為三個階段,然後詳細講解每個階段具體呼叫了什麼函式,這三個階段是:
- 掛載階段
- 更新階段
- 解除安裝階段
掛載階段
掛載階段,也可以理解為元件的初始化階段,就是將我們的元件插入到DOM中,只會發生一次
這個階段的生命週期函式呼叫如下:
- constructor
- getDerivedStateFromProps
componentWillMount/UNSAVE_componentWillMount- render
- componentDidMount
constructor
元件建構函式,第一個被執行
如果沒有顯示定義它,我們會擁有一個預設的建構函式
如果顯示定義了建構函式,我們必須在建構函式第一行執行super(props),否則我們無法在建構函式裡拿到this物件,這些都屬於ES6的知識
在建構函式裡面我們一般會做兩件事:
- 初始化state物件
- 給自定義方法繫結this
constructor(props) {
super(props)
this.state = {
select,
height: 'atuo',
externalClass,
externalClassText
}
this.handleChange1 = this.handleChange1.bind(this)
this.handleChange2 = this.handleChange2.bind(this)
}
複製程式碼
禁止在建構函式中呼叫setState,可以直接給state設定初始值
getDerivedStateFromProps
static getDerivedStateFromProps(nextProps, prevState)
一個靜態方法,所以不能在這個函式裡面使用this,這個函式有兩個引數props和state,分別指接收到的新引數和當前的state物件,這個函式會返回一個物件用來更新當前的state物件,如果不需要更新可以返回null
該函式會在掛載時,接收到新的props,呼叫了setState和forceUpdate時被呼叫
在React v16.3時只有在掛載時和接收到新的props被呼叫,據說這是官方的失誤,後來修復了
這個方法就是為了取代之前的componentWillMount、componentWillReceiveProps和componentWillUpdate
當我們接收到新的屬性想去修改我們state,可以使用getDerivedStateFromProps
class ExampleComponent extends React.Component {
state = {
isScrollingDown: false,
lastRow: null
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.currentRow !== prevState.lastRow) {
return {
isScrollingDown:
nextProps.currentRow > prevState.lastRow,
lastRow: nextProps.currentRow
}
}
return null
}
}
複製程式碼
componentWillMount/UNSAFE_componentWillMount
在16版本這兩個方法並存,但是在17版本中componentWillMount被刪除,只保留UNSAFE_componentWillMount,目的是為了做向下相容,對於新的應用,用getDerivedStateFromProps代替它們
由於componentWillMount/ UNSAFE_componentWillMount是在render之前呼叫,所以就算在這個方法中呼叫setState也不會觸發重新渲染(re-render)
render
React中最核心的方法,一個元件中必須要有這個方法
返回的型別有以下幾種:
- 原生的DOM,如div
- React元件
- Fragment(片段)
- Portals(插槽)
- 字串和數字,被渲染成text節點
- Boolean和null,不會渲染任何東西
關於Fragment和Portals是React16新增的,如果大家不清楚可以去閱讀官方文件,在這裡就不展開了
render函式是純函式,裡面只做一件事,就是返回需要渲染的東西,不應該包含其它的業務邏輯,如資料請求,對於這些業務邏輯請移到componentDidMount和componentDid Update中
componentDidMount
元件裝載之後呼叫,此時我們可以獲取到DOM節點並操作,比如對canvas,svg的操作,伺服器請求,訂閱都可以寫在這個裡面,但是記得在componentWillUnmount中取消訂閱
componentDidMount() {
const { progressCanvas, progressSVG } = this
const canvas = progressCanvas.current
const ctx = canvas.getContext('2d')
canvas.width = canvas.getBoundingClientRect().width
canvas.height = canvas.getBoundingClientRect().height
const svg = progressSVG.current
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
rect.setAttribute('x', 0)
rect.setAttribute('y', 0)
rect.setAttribute('width', 0)
rect.setAttribute('height', svg.getBoundingClientRect().height)
rect.setAttribute('style', 'fill:red')
const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animate')
animate.setAttribute('attributeName', 'width')
animate.setAttribute('from', 0)
animate.setAttribute('to', svg.getBoundingClientRect().width)
animate.setAttribute('begin', '0ms')
animate.setAttribute('dur', '1684ms')
animate.setAttribute('repeatCount', 'indefinite')
animate.setAttribute('calcMode', 'linear')
rect.appendChild(animate)
svg.appendChild(rect)
svg.pauseAnimations()
this.canvas = canvas
this.svg = svg
this.ctx = ctx
}
複製程式碼
在componentDidMount中呼叫setState會觸發一次額外的渲染,多呼叫了一次render函式,但是使用者對此沒有感知,因為它是在瀏覽器重新整理螢幕前執行的,但是我們應該在開發中避免它,因為它會帶來一定的效能問題,我們應該在constructor中初始化我們的state物件,而不應該在componentDidMount呼叫state方法
更新階段
更新階段,當元件的props改變了,或元件內部呼叫了setState或者forceUpdate發生,會發生多次
這個階段的生命週期函式呼叫如下:
componentWillReceiveProps/UNSAFE_componentWillReceiveProps- getDerivedStateFromProps
- shouldComponentUpdate
componentWillUpdate/UNSAFE_componentWillUpdate- render
- getSnapshotBeforeUpdate
- componentDidUpdate
componentWillReceiveProps/UNSAFE_componentWillReceiveProps
componentWillReceiveProps(nextProps, prevState) UNSAFE_componentWillReceiveProps(nextProps, prevState)
在16版本這兩個方法並存,但是在17版本中componentWillReceiveProps被刪除,UNSAFE_componentWillReceiveProps,目的是為了做向下相容,對於新的應用,用getDerivedStateFromProps代替它們
注意,當我們父元件重新渲染的時候,也會導致我們的子元件呼叫componentWillReceiveProps/UNSAFE_componentWillReceiveProps,即使我們的屬性和之前的一樣,所以需要我們在這個方法裡面去進行判斷,如果前後屬性不一致才去呼叫setState
在裝載階段這兩個函式不會被觸發,在元件內部呼叫了setState和forceUpdate也不會觸發這兩個函式
getDerivedStateFromProps
這個方法在裝載階段已經講過了,這裡不再贅述,記住在更新階段,無論我們接收到新的屬性,呼叫了setState還是呼叫了forceUpdate,這個方法都會被呼叫
shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState)
有兩個引數nextProps和nextState,表示新的屬性和變化之後的state,返回一個布林值,true表示會觸發重新渲染,false表示不會觸發重新渲染,預設返回true
注意當我們呼叫forceUpdate並不會觸發此方法
因為預設是返回true,也就是隻要接收到新的屬性和呼叫了setState都會觸發重新的渲染,這會帶來一定的效能問題,所以我們需要將this.props與nextProps以及this.state與nextState進行比較來決定是否返回false,來減少重新渲染
但是官方提倡我們使用PureComponent來減少重新渲染的次數而不是手工編寫shouldComponentUpdate程式碼,具體該怎麼選擇,全憑開發者自己選擇
在未來的版本,shouldComponentUpdate返回false,仍然可能導致元件重新的渲染,這是官方自己說的
Currently, if shouldComponentUpdate() returns false, then UNSAFE_componentWillUpdate(), render(), and componentDidUpdate() will not be invoked. In the future React may treat shouldComponentUpdate() as a hint rather than a strict directive, and returning false may still result in a re-rendering of the component.
componentWillUpdate/UNSAFE_componentWillUpdate
componentWillUpdate(nextProps, nextState)
UNSAFE_componentWillUpdate(nextProps, nextState)
在16版本這兩個方法並存,但是在17版本中componentWillUpdate被刪除,UNSAFE_componentWillUpdate,目的是為了做向下相容
在這個方法裡,你不能呼叫setState,因為能走到這個方法,說明shouldComponentUpdate返回true,此時下一個state狀態已經被確定,馬上就要執行render重新渲染了,否則會導致整個生命週期混亂,在這裡也不能請求一些網路資料,因為在非同步渲染中,可能會導致網路請求多次,引起一些效能問題,
如果你在這個方法裡儲存了滾動位置,也是不準確的,還是因為非同步渲染的問題,如果你非要獲取滾動位置的話,請在getSnapshotBeforeUpdate呼叫
render
更新階段也會觸發,裝載階段已經講過了,不再贅述
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState)
這個方法在render之後,componentDidUpdate之前呼叫,有兩個引數prevProps和prevState,表示之前的屬性和之前的state,這個函式有一個返回值,會作為第三個引數傳給componentDidUpdate,如果你不想要返回值,請返回null,不寫的話控制檯會有警告
還有這個方法一定要和componentDidUpdate一起使用,否則控制檯也會有警告
前面說過這個方法時用來代替componentWillUpdate/UNSAVE_componentWillUpdate,下面舉個例子說明下:
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
複製程式碼
componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)
該方法在getSnapshotBeforeUpdate方法之後被呼叫,有三個引數prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三個引數是getSnapshotBeforeUpdate返回的
在這個函式裡我們可以操作DOM,和發起伺服器請求,還可以setState,但是注意一定要用if語句控制,否則會導致無限迴圈
解除安裝階段
解除安裝階段,當我們的元件被解除安裝或者銷燬了
這個階段的生命週期函式只有一個:
- componentWillUnmount
componentWillUnmount
當我們的元件被解除安裝或者銷燬了就會呼叫,我們可以在這個函式裡去清除一些定時器,取消網路請求,清理無效的DOM元素等垃圾清理工作
注意不要在這個函式裡去呼叫setState,因為元件不會重新渲染了
最後
你們的打賞是我寫作的動力