細說React生命周期
- 2020 年 10 月 30 日
- 筆記
- javascript, React
新舊版本生命周期圖對比
16.3之前的版本
16.3之後的版本
react自16.x之後,添加了getDerivedStateFromProps, getSnapshotBeforeUpdate
,如圖
生命周期的幾個階段
生命周期有幾個時期,分別為掛載,更新,卸載,我們看上方16.3之後版本的圖可以得出,其實每個時期還分為幾個階段:
- render階段: 是指從到render函數執行完返回jsx結構之前的過程,這個過程的都是不包含副作用的純函數,這個過程包含的函數有
- constructor
- getDerivedStateFromProps
- shouldUpdateComponent
- render
- getDerivedStateFromError
- pre-commit階段: 是指從返回一個jsx結構到更新dom之前的過程,這個過程只有函數
getSnapshotBeforeUpdate
- commit階段: 是指更新dom,以及更新成功之後的過程,有函數
- componentDidMount
- componentDidUpdate
- componentDidCatch
從上圖中看函數componentWillUnmount
是在commit階段但是,我理解這個函數應該是在dom更新之前執行,我認為應該在pre-commit
掛載
所謂掛載其實就是組件第一次在dom樹種渲染的過程,在這個過程中會一次執行一些生命周期函數
constructor
構造函數用於初始化props,state以及綁定this指向,如果不需要這些操作可以省略構造函數,需要注意的是
- 構造函數中不能調用setState, 而應該直接給state賦值
- props是由外部傳進來的,所以不能修改props
- 不能直將props中的值賦值給state,像這樣
constructor(props) {
super(props);
// 不要這樣做
this.state = { color: props.color };
}
conpomentWillMount(v17將移除)
這個方法會在render之前執行,這個方法也是在服務端渲染中唯一會調用的方法
getDerivedStateFromProps(v16.3加入)
conpomentWillMount
移除了取而代之的就是這個方法,這是一個靜態方法,實例無權訪問,在組件實例化之後,以及每次render
之前調用。getDerivedStateFromProps
的存在只有一個目的就是讓props更新的時候更新state,在這一點上跟componentWillReceiveProps
是一致的,這兩個生命周期函數並不是只有在props更新的時候才會調用,而是當父組件重新渲染後,兩個生命周期函數都會調用
static getDerivedStateFromProps(props, state)
render
組件的render函數並不會像dom中插入元素,實際上render只是返回jsx結構,或者是數組,字符串等數據,最終由ReactDOM.render渲染到dom中,在自定義的類組件中,render函數必須實現,並且render應該為一個根據state和props來返回結果,不造成副作用的純函數
componentDidMount
在組件被掛載(插入到dom節點)後,立刻執行這個函數,可以在這個函數中請求數據,以及添加訂閱,但是要記得在函數componentWillUnmount
中去取消訂閱
更新
當組件的props更新時,或者state更新時也會觸發一些生命周期函數來更新dom
props更新 componentWillReceiveProps(v17將移除)
當父組件的render被調用時,無論傳進來的props是否更新,這個函數都會調用,需要注意的是,在函數調用時,應該將參數與props做一個對比,判斷是否要更新state
componentWillReceiveProps(nextProps) {
// 只要 props.email 改變,就改變 state
if (nextProps.email !== this.props.email) {
this.setState({
email: nextProps.email
});
}
}
這個方法state更新時不會執行,v17之後由getDerivedStateFromProps
取代
shouldComponentUpdate
這個函數返回一個布爾值,返回true的時候接下來會執行組件的render
方法,默認情況下都是返回true的,如果為false則不會執行後續渲染,這個方法在掛載階段或者執行forceUpdate
時不會執行。
shouldComponentUpdate(nextProps, nextState)
函數接受的參數為新的props
和新的state
所以在函數內部可以拿新的props和state與組件當前的props、state做一個對比,如果發現沒有變化則返回false。React官方建議使用React.PureComponent
實現組件,這個組件的shouldComponentUpdate
方法會對props和state分別做淺層對比,以此來提高性能,同時不建議在shouldComponentUpdate
中去實現深層次的數據對比,以免影響效率
componentWillUpdate(v17將移除)
shouldComponentUpdate
返回true
,則會執行componentWillUpdate
,在這個方法中,不能執行setState
等其它會更新組件的方法(比如dispatch),可以在此方法中讀取 DOM 信息(例如,為了保存滾動位置),而在之後可以放到getSnapshotBeforeUpdate
中
getSnapshotBeforeUpdate(v16.3引入)
getSnapshotBeforeUpdate(prevProps, prevState)
在render之後,dom更新之前回執行這個函數,一般會在此時讀取dom更新之前的一些信息(比如滾動條位置),此函數的所有返回值,都將作為參數傳給componentDidUpdate
componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)
這裡第三個參數就為上文中getSnapshotBeforeUpdate
的返回值,componentDidUpdate
會在dom加載完之後執行,在這個函數中就可以進行dom操作了
卸載
componentWillUnmount
組件銷毀之前調用,在這裡更新state不會更新組件
錯誤處理
react在v16引入了錯誤邊界的概念,當子組件發生,當子組件的構造函數,生命周期以及子組件的構造函數出出錯的時候,會執行錯誤處理回調
getDerivedStateFromError
static getDerivedStateFromError(error)
當子組件拋出錯誤會觸發這個函數,錯誤信息為函數的參數,次函數可以返回一個新的值來更新state,組件可以根據更新的state實現一個降級UI,此方法在「render階段」執行,應該為一個純函數
componentDidCatch
componentDidCatch(error, info)
同樣當子組件拋出錯誤的時候回觸發,第二個參數為info,其中包含了組件調用棧的信息
console.log(info.componentStack);
這個函數是在「commit階段」執行的,所以可以在函數中調用setState來更新state
為什麼react要在v17移除部分構造函數?
官方團隊認為,之前的幾個will
生命周期函數會讓用戶產生很多誤解,在實際的使用場景中,也有很多錯誤的使用情況出現。所以直接替換為一個靜態方法getDerivedStateFromProps,這樣用戶就不能再這些方法內訪問到當前的實例了,從而也就不能調用setState,或者訪問refs了,減少了很多錯誤操作
其他原因可以參考鏈接