React16.3新的生命週期詳解
也就是以下程式碼中類的構造方法( 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); } }
1.2 第二個是元件的掛載(Mounting)階段
此階段分為componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate,render,componentDidUpdate
1.2.1 componentWillMount
在元件掛載到DOM前呼叫,且只會被呼叫一次,在這邊呼叫 this.setState
不會引起元件重新渲染,也可以把寫在這邊的內容提前到 constructor()
中,所以專案中很少用
1.2.2 render
根據元件的 props
和 state
(無兩者的重傳遞和重賦值,論值是否有變化,都可以引起元件重新 render
) , return
一個 React
元素(描述元件,即 UI
),不負責元件實際渲染工作,之後由 React
自身根據此元素去渲染出頁面 DOM
。 render
是純函式( Pure function
:函式的返回結果只依賴於它的引數;函式執行過程裡面沒有副作用),不能在裡面執行 this.setState
,會有改變元件狀態的副作用
1.2.3 componentDidMount
元件掛載到 DOM
後呼叫,且只會被呼叫一次
1.3 第三個是元件的更新(update)階段
setState
引起的 state
更新或父元件重新 render
引起的 props
更新,更新後的 state
和 props
相對之前無論是否有變化,都將引起子元件的重新 render
1.3.1 造成元件更新有兩類(三種)情況
1. 父元件重新render
父元件重新 render
引起子元件重新 render
的情況有兩種
- 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> } }
1.3.2 componentWillReceiveProps(nextProps)
此方法只調用於 props
引起的元件更新過程中,引數 nextProps
是父元件傳給當前元件的新 props
。但父元件 render
方法的呼叫不能保證重傳給當前元件的 props
是有變化的,所以在此方法中根據 nextProps
和 this.props
來查明重傳的 props
是否改變,以及如果改變了要執行啥,比如根據新的 props
呼叫 this.setState
出發當前元件的重新 render
1.3.3 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
了
1.3.4 componentWillUpdate(nextProps, nextState)
此方法在呼叫 render
方法前執行,在這邊可執行一些元件更新發生前的工作,一般較少用
1.3.5 componentDidUpdate(prevProps, prevState)
此方法在元件更新後被呼叫,可以操作元件更新的 DOM
, prevProps
和 prevState
這兩個引數指的是元件更新前的 props
和 state
1.4 解除安裝階段
此階段只有一個生命週期方法: componentWillUnmount
此方法在元件被解除安裝前呼叫,可以在這裡執行一些清理工作,比如清楚元件中使用的定時器,清除 componentDidMount
中手動建立的 DOM
元素等,以避免引起記憶體洩漏
二、React v16.4 的生命週期
React v16.4 的生命週期圖
2.1 變更緣由
原來( 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
在伺服器端渲染也會被呼叫到
- 所以除了
shouldComponentUpdate
,其他在render
函式之前的所有函式(componentWillMount
,componentWillReceiveProps
,componentWillUpdate
)都被getDerivedStateFromProps
替代 - 也就是用一個靜態函式
getDerivedStateFromProps
來取代被deprecate
的幾個生命週期函式,就是強制開發者在render
之前只做無副作用的操作,而且能做的操作侷限在根據props
和state
決定新的state
React v16.0剛推出的時候,是增加了一個 componentDidCatch
生命週期函式,這只是一個增量式修改,完全不影響原有生命週期函式;但是,到了 React v16.3
,大改動來了,引入了兩個新的生命週期函式
2.2 引入了兩個新的生命週期函式
2.2.1 getDerivedStateFromProps
getDerivedStateFromProps
本來(React v16.3中)是隻在建立和更新(由父元件引發部分),也就是不是不由父元件引發,那麼 getDerivedStateFromProps
也不會被呼叫,如自身 setState
引發或者 forceUpdate
引發
React v16.3 的生命週期圖
這樣的話理解起來有點亂,在 React v16.4
中改正了這一點,讓 getDerivedStateFromProps
無論是 Mounting
還是 Updating
,也無論是因為什麼引起的 Updating
,全部都會被呼叫,具體可看 React v16.4
的生命週期圖
React v16.4後的getDerivedStateFromProps
static getDerivedStateFromProps(props, state)
在元件建立時和更新時的render方法之前呼叫,它應該返回一個物件來更新狀態,或者返回 null
來不更新任何內容
2.2.2 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> ); } }