詳解React生命周期(包括react16最新版)
React v16.0前的生命周期
其實大部分團隊不見得會跟進升到16版本,所以16前的生命周期還是很有必要掌握的,何況16也是基於之前的修改

第一個是組件初始化(initialization)階段
也就是以下代碼中類的構造方法( constructor() ),Test類繼承了react Component這個基類,也就繼承這個react的基類,才能有render(),生命周期等方法可以使用,這也說明為什么函數組件不能使用這些方法
的原因。
super(props)
用來調用基類的構造方法( constructor() ), 也將父組件的props注入給子組件,功子組件讀取(組件中props只讀不可變,state可變)。
而constructor()
用來做一些組件的初始化工作,如定義this.state的初始內容。
import React, { Component } from 'react'; class Test extends Component { constructor(props) { super(props); } }
第二個是組件的掛載(Mounting)階段
此階段分為componentWillMount,render,componentDidMount三個時期。
- componentWillMount:
在組件掛載到DOM前調用,且只會被調用一次,在這邊調用this.setState不會引起組件重新渲染,也可以把寫在這邊的內容提前到constructor()中,所以項目中很少用。
- render:
根據組件的props和state(無兩者的重傳遞和重賦值,論值是否有變化,都可以引起組件重新render) ,return 一個React元素(描述組件,即UI),不負責組件實際渲染工作,之后由React自身根據此元素去渲染出頁面DOM。render是純函數(Pure function:函數的返回結果只依賴於它的參數;函數執行過程里面沒有副作用),不能在里面執行this.setState,會有改變組件狀態的副作用。
- componentDidMount:
組件掛載到DOM后調用,且只會被調用一次
第三個是組件的更新(update)階段
在講述此階段前需要先明確下react組件更新機制。setState引起的state更新或父組件重新render引起的props更新,更新后的state和props相對之前無論是否有變化,都將引起子組件的重新render。詳細可看這篇文章
造成組件更新有兩類(三種)情況:
- 1.父組件重新render
父組件重新render引起子組件重新render的情況有兩種,內容及代碼修引自xiaoyann的回答
a. 直接使用,每當父組件重新render導致的重傳props,子組件將直接跟着重新渲染,無論props是否有變化。可通過shouldComponentUpdate方法優化。
class Child extends Component { shouldComponentUpdate(nextProps){ // 應該使用這個方法,否則無論props是否有變化都將會導致組件跟着重新渲染 if(nextProps.someThings === this.props.someThings){ return false } } render() { return <div>{this.props.someThings}</div> } }
b.在componentWillReceiveProps方法中,將props轉換成自己的state
class Child extends Component { constructor(props) { super(props); this.state = { someThings: props.someThings }; } componentWillReceiveProps(nextProps) { // 父組件重傳props時就會調用這個方法 this.setState({someThings: nextProps.someThings}); } render() { return <div>{this.state.someThings}</div> } }
根據官網的描述
在該函數(componentWillReceiveProps)中調用 this.setState() 將不會引起第二次渲染。
是因為componentWillReceiveProps中判斷props是否變化了,若變化了,this.setState將引起state變化,從而引起render,此時就沒必要再做第二次因重傳props引起的render了,不然重復做一樣的渲染了。
- 2.組件本身調用setState,無論state有沒有變化。可通過shouldComponentUpdate方法優化。
class Child extends Component { constructor(props) { super(props); this.state = { someThings:1 } } shouldComponentUpdate(nextStates){ // 應該使用這個方法,否則無論state是否有變化都將會導致組件重新渲染 if(nextStates.someThings === this.state.someThings){ return false } } handleClick = () => { // 雖然調用了setState ,但state並無變化 const preSomeThings = this.state.someThings this.setState({ someThings: preSomeThings }) } render() { return <div onClick = {this.handleClick}>{this.state.someThings}</div> } }
此階段分為componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate,render,componentDidUpdate
- componentWillReceiveProps(nextProps)
此方法只調用於props引起的組件更新過程中,參數nextProps是父組件傳給當前組件的新props。但父組件render方法的調用不能保證重傳給當前組件的props是有變化的,所以在此方法中根據nextProps和this.props來查明重傳的props是否改變,以及如果改變了要執行啥,比如根據新的props調用this.setState出發當前組件的重新render
- shouldComponentUpdate(nextProps, nextState)
此方法通過比較nextProps,nextState及當前組件的this.props,this.state,返回true時當前組件將繼續執行更新過程,返回false則當前組件更新停止,以此可用來減少組件的不必要渲染,優化組件性能。
ps:這邊也可以看出,就算componentWillReceiveProps()中執行了this.setState,更新了state,但在render前(如shouldComponentUpdate,componentWillUpdate),this.state依然指向更新前的state,不然nextState及當前組件的this.state的對比就一直是true了。
- componentWillUpdate(nextProps, nextState)
此方法在調用render方法前執行,在這邊可執行一些組件更新發生前的工作,一般較少用。
- render
render方法在上文講過,這邊只是重新調用。
- componentDidUpdate(prevProps, prevState)
此方法在組件更新后被調用,可以操作組件更新的DOM,prevProps和prevState這兩個參數指的是組件更新前的props和state
卸載階段
此階段只有一個生命周期方法:componentWillUnmount
- componentWillUnmount
此方法在組件被卸載前調用,可以在這里執行一些清理工作,比如清楚組件中使用的定時器,清楚componentDidMount中手動創建的DOM元素等,以避免引起內存泄漏。
React v16.4 的生命周期
變更緣由
原來(React v16.0前)的生命周期在React v16推出的Fiber之后就不合適了,因為如果要開啟async rendering,在render函數之前的所有函數,都有可能被執行多次。
原來(React v16.0前)的生命周期有哪些是在render前執行的呢?
- componentWillMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
如果開發者開了async rendering,而且又在以上這些render前執行的生命周期方法做AJAX請求的話,那AJAX將被無謂地多次調用。。。明顯不是我們期望的結果。而且在componentWillMount里發起AJAX,不管多快得到結果也趕不上首次render,而且componentWillMount在服務器端渲染也會被調用到(當然,也許這是預期的結果),這樣的IO操作放在componentDidMount里更合適。
禁止不能用比勸導開發者不要這樣用的效果更好,所以除了shouldComponentUpdate,其他在render函數之前的所有函數(componentWillMount,componentWillReceiveProps,componentWillUpdate)都被getDerivedStateFromProps替代。
也就是用一個靜態函數getDerivedStateFromProps來取代被deprecate的幾個生命周期函數,就是強制開發者在render之前只做無副作用的操作,而且能做的操作局限在根據props和state決定新的state
React v16.0剛推出的時候,是增加了一個componentDidCatch生命周期函數,這只是一個增量式修改,完全不影響原有生命周期函數;但是,到了React v16.3,大改動來了,引入了兩個新的生命周期函數: getDerivedStateFromProps
,getSnapshotBeforeUpdate
getDerivedStateFromProps
getDerivedStateFromProps
本來(React v16.3中)是只在創建和更新(由父組件引發部分),也就是不是不由父組件引發,那么getDerivedStateFromProps也不會被調用,如自身setState引發或者forceUpdate引發。
React v16.3 的生命周期圖

這樣的話理解起來有點亂,在React v16.4中改正了這一點,讓getDerivedStateFromProps無論是Mounting還是Updating,也無論是因為什么引起的Updating,全部都會被調用,具體可看React v16.4 的生命周期圖。
React v16.4+ 的生命周期圖

React v16.4后的getDerivedStateFromProps
static getDerivedStateFromProps(props, state) 在組件創建時和更新時的render方法之前調用,它應該返回一個對象來更新狀態,或者返回null來不更新任何內容。
注意:
- getDerivedStateFromProps前面要加上static保留字,聲明為靜態方法,不然會被react忽略掉

- getDerivedStateFromProps里面的this為undefined
static靜態方法只能Class(構造函數)來調用(App.staticMethod✅),而實例是不能的( (new App()).staticMethod ❌ );
當調用React Class組件時,改組件會實例化;
所以
React Class組件中,靜態方法getDerivedStateFromProps無權訪問Class實例的this,即this為undefined。
可以看react issue相關討論 https://github.com/facebook/react/issues/12612 https://github.com/facebook/react/issues/14730
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate() 被調用於render之后,可以讀取但無法使用DOM的時候。它使您的組件可以在可能更改之前從DOM捕獲一些信息(例如滾動位置)。此生命周期返回的任何值都將作為參數傳遞給componentDidUpdate()。
官網給的例子:
class ScrollingList extends React.Component { constructor(props) { super(props); this.listRef = React.createRef(); } getSnapshotBeforeUpdate(prevProps, prevState) { //我們是否要添加新的 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> ); } }
參考鳴謝
程墨Morgan老師的React v16.3之后的組件生命周期函數
徐超老師的《React進階之路》