React新生命周期--getDerivedStateFromProps、getSnapshotBeforeUpdate

在說新的生命周期之前,我們先了解下原來的生命周期:

react生命周期.jpg
  1. 掛載卸載過程
    1.1.constructor()
    constructor()中完成了React數(shù)據(jù)的初始化,它接受兩個(gè)參數(shù):props和context,當(dāng)想在函數(shù)內(nèi)部使用這兩個(gè)參數(shù)時(shí),需使用super()傳入這兩個(gè)參數(shù)。
    注意:只要使用了constructor()就必須寫super(),否則會(huì)導(dǎo)致this指向錯(cuò)誤。

1.2.componentWillMount()
componentWillMount()一般用的比較少,它更多的是在服務(wù)端渲染時(shí)使用。它代表的過程是組件已經(jīng)經(jīng)歷了constructor()初始化數(shù)據(jù)后,但是還未渲染DOM時(shí)。

1.3.componentDidMount()
組件第一次渲染完成,此時(shí)dom節(jié)點(diǎn)已經(jīng)生成,可以在這里調(diào)用ajax請(qǐng)求,返回?cái)?shù)據(jù)setState后組件會(huì)重新渲染

1.4.componentWillUnmount ()
在此處完成組件的卸載和數(shù)據(jù)的銷毀。

  • clear你在組建中所有的setTimeout,setInterval
  • 移除所有組建中的監(jiān)聽 removeEventListener
  • 有時(shí)候我們會(huì)碰到這個(gè)warning:
Can only update a mounted or mounting component. This usually      means you called setState() on an unmounted component. This is a   no-op. Please check the code for the undefined component.

原因:因?yàn)槟阍诮M件中的ajax請(qǐng)求返回setState,而你組件銷毀的時(shí)候,請(qǐng)求還未完成,因此會(huì)報(bào)warning
解決方法:

componentDidMount() {
    this.isMount === true
    axios.post().then((res) => {
    this.isMount && this.setState({   // 增加條件ismount為true時(shí)
      aaa:res
    })
})
}
componentWillUnmount() {
    this.isMount === false
}
  1. 更新過程
    2.1. componentWillReceiveProps (nextProps)
  • 在接受父組件改變后的props需要重新渲染組件時(shí)用到的比較多
  • 接受一個(gè)參數(shù)nextProps
  • 通過對(duì)比nextProps和this.props,將nextProps的state為當(dāng)前組件的state,從而重新渲染組件
componentWillReceiveProps (nextProps) {
    nextProps.openNotice !== this.props.openNotice&&this.setState({
        openNotice:nextProps.openNotice
    },() => {
      console.log(this.state.openNotice:nextProps)
      //將state更新為nextProps,在setState的第二個(gè)參數(shù)(回調(diào))可以打         印出新的state
  })
}

2.2.shouldComponentUpdate(nextProps,nextState)

  • 主要用于性能優(yōu)化(部分更新)
  • 唯一用于控制組件重新渲染的生命周期,由于在react中,setState以后,state發(fā)生變化,組件會(huì)進(jìn)入重新渲染的流程,在這里return false可以阻止組件的更新
  • 因?yàn)閞eact父組件的重新渲染會(huì)導(dǎo)致其所有子組件的重新渲染,這個(gè)時(shí)候其實(shí)我們是不需要所有子組件都跟著重新渲染的,因此需要在子組件的該生命周期中做判斷

2.3.componentWillUpdate (nextProps,nextState)
shouldComponentUpdate返回true以后,組件進(jìn)入重新渲染的流程,進(jìn)入componentWillUpdate,這里同樣可以拿到nextProps和nextState。

2.4.componentDidUpdate(prevProps,prevState)
組件更新完畢后,react只會(huì)在第一次初始化成功會(huì)進(jìn)入componentDidmount,之后每次重新渲染后都會(huì)進(jìn)入這個(gè)生命周期,這里可以拿到prevProps和prevState,即更新前的props和state。

2.5.render()
render函數(shù)會(huì)插入jsx生成的dom結(jié)構(gòu),react會(huì)生成一份虛擬dom樹,在每一次組件更新時(shí),在此react會(huì)通過其diff算法比較更新前后的新舊DOM樹,比較以后,找到最小的有差異的DOM節(jié)點(diǎn),并重新渲染。

接下來我們看看更新后的生命周期

react新的生命周期.png

先看看它的變化:

新增:getDerivedStateFromProps,getSnapshotBeforeUpdate
UNSAFE:UNSAFE_componentWillMount,UNSAFE_componentWillUpdate,UNSAFE_componentWillReceiveProps

React 官方正式發(fā)布了 v16.3 版本。在這次的更新中,除了前段時(shí)間被熱烈討論的新 Context API 之外,新引入的兩個(gè)生命周期函數(shù) getDerivedStateFromProps,getSnapshotBeforeUpdate 以及在未來 v17.0 版本中即將被移除的三個(gè)生命周期函數(shù) componentWillMount,componentWillReceiveProps,componentWillUpdate .

getDerivedStateFromProps

React生命周期的命名一直都是非常語義化的,這個(gè)生命周期的意思就是從props中獲取state,可以說是太簡(jiǎn)單易懂了??梢哉f,這個(gè)生命周期的功能實(shí)際上就是將傳入的props映射到state上面。由于16.4的修改,這個(gè)函數(shù)會(huì)在每次re-rendering之前被調(diào)用,這意味著什么呢?意味著即使你的props沒有任何變化,而是父state發(fā)生了變化,導(dǎo)致子組件發(fā)生了re-render,這個(gè)生命周期函數(shù)依然會(huì)被調(diào)用??此埔粋€(gè)非常小的修改,卻可能會(huì)導(dǎo)致很多隱含的問題。

使用

這個(gè)生命周期函數(shù)是為了替代componentWillReceiveProps存在的,所以在你需要使用componentWillReceiveProps的時(shí)候,就可以考慮使用getDerivedStateFromProps來進(jìn)行替代了。

兩者的參數(shù)是不相同的,而getDerivedStateFromProps是一個(gè)靜態(tài)函數(shù),也就是這個(gè)函數(shù)不能通過this訪問到class的屬性,也并不推薦直接訪問屬性。而是應(yīng)該通過參數(shù)提供的nextProps以及prevState來進(jìn)行判斷,根據(jù)新傳入的props來映射到state。

需要注意的是,如果props傳入的內(nèi)容不需要影響到你的state,那么就需要返回一個(gè)null,這個(gè)返回值是必須的,所以盡量將其寫到函數(shù)的末尾。

// 在getDerivedStateFromProps中進(jìn)行state的改變
  static getDerivedStateFromProps({ error, value }, state) {
    /* Keep last received error in state */
    if (error && error !== state.error) {
      return { error };
    }
    if (value && value !== state.text) {
      return { text: value }
    }

    return null;
  }
Case1 -- 多來源的不同狀態(tài)

假設(shè)我們有一個(gè)列表,這個(gè)列表受到頁面主體,也就是根組件的驅(qū)動(dòng),也受到其本身數(shù)據(jù)加載的驅(qū)動(dòng)。

因?yàn)檫@個(gè)頁面在開始渲染的時(shí)候,所有的數(shù)據(jù)請(qǐng)求可能是通過batch進(jìn)行的,所以要在根組件進(jìn)行統(tǒng)一處理,而其列表的分頁操作,則是由其本身控制。

這會(huì)出現(xiàn)什么問題呢?該列表的狀態(tài)受到兩方面的控制,也就是re-render可能由props驅(qū)動(dòng),也可能由state驅(qū)動(dòng)。這就導(dǎo)致了getDerivedStateFromProps會(huì)在兩種驅(qū)動(dòng)狀態(tài)下被重新渲染。

當(dāng)這個(gè)函數(shù)被多次調(diào)用的時(shí)候,就需要注意到,state和props的變化將會(huì)怎樣影響到你的組件變化。

// 組件接收一個(gè)type參數(shù)
static propTypes = {
    type: PropTypes.number
}

// 組件還具有自己的狀態(tài)來渲染列表
class List extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            list: [],
            type: 0,
        }
    }
}

如上面代碼的例子所示,組件既受控,又控制自己。當(dāng)type發(fā)生變化,會(huì)觸發(fā)一次getDerivedStateFromProps,在這里更新組件的type狀態(tài),然而,在進(jìn)行異步操作之后,組件又會(huì)更新list狀態(tài),這時(shí)你的getDerivedStateFromProps函數(shù)就需要注意,不能夠僅僅判斷type是否變化來更新狀態(tài),因?yàn)閘ist的變化也會(huì)更新到組件的狀態(tài)。這時(shí)就必須返回一個(gè)null,否則會(huì)導(dǎo)致組件無法更新并且報(bào)錯(cuò)。

Case2 -- 組織好你的組件

考慮一下,如果你的組件內(nèi)部既需要修改自己的type,又需要接收從外部修改的type。

是不是非常混亂?getDerivedStateFromProps中你根本不知道該做什么。

static getDerivedStateFromProps(nextProps, prevState) {
    const {type} = nextProps;
    // type可能由props驅(qū)動(dòng),也可能由state驅(qū)動(dòng),這樣判斷會(huì)導(dǎo)致state驅(qū)動(dòng)的type被回滾
    if (type !== prevState.type) {
        return {
            type,
        };
    }
    // 否則,對(duì)于state不進(jìn)行任何操作
    return null;
}

如何解決這個(gè)棘手的問題呢?

好好組織你的組件,在非必須的時(shí)候,摒棄這種寫法。type要么由props驅(qū)動(dòng),要么完全由state驅(qū)動(dòng)。
如果實(shí)在沒有辦法解耦,那么就需要一個(gè)hack來輔助:綁定props到state上。

constructor(props) {
    super(props);
    this.state = {
        type: 0,
        props,
    }
}
static getDerivedStateFromProps(nextProps, prevState) {
    const {type, props} = nextProps;
    // 這段代碼可能看起來非?;靵y,這個(gè)props可以被當(dāng)做緩存,僅用作判斷
    if (type !== props.type) {
        return {
            type,
            props: {
                type,
            },
        };
    }
    // 否則,對(duì)于state不進(jìn)行任何操作
    return null;
}

上面的代碼可以保證在進(jìn)行多數(shù)據(jù)源驅(qū)動(dòng)的時(shí)候,狀態(tài)能夠正確改變。當(dāng)然,這樣的代碼很多情況下是會(huì)影響到別人閱讀你的代碼的,對(duì)于維護(hù)造成了非常大的困難。

從這個(gè)生命周期的更新來看,react更希望將受控的propsstate進(jìn)行分離,就如同Redux作者Dan Abramov在redux文檔當(dāng)中寫的一樣Presentational and Container Components,將所有的組件分離稱為展示型組件和容器型組件,一個(gè)只負(fù)責(zé)接收props來改變自己的樣式,一個(gè)負(fù)責(zé)保持其整個(gè)模塊的state。這樣可以讓代碼更加清晰。但是在實(shí)際的業(yè)務(wù)邏輯中,我們有時(shí)很難做到這一點(diǎn),而且這樣可能會(huì)導(dǎo)致容器型組件變得非常龐大以致難以管理,如何進(jìn)行取舍還是需要根據(jù)實(shí)際場(chǎng)景決定的。

Case3 -- 異步

以前,我們可以在props發(fā)生改變的時(shí)候,在componentWillReceiveProps中進(jìn)行異步操作,將props的改變驅(qū)動(dòng)到state的改變。

componentWillReceiveProps(nextProps) {
    if (props.type !== nextProps.type) {
        // 在這里進(jìn)行異步操作或者更新狀態(tài)
        this.setState({
            type: props.type,
        });
        this._doAsyncOperation();
    }
}

這樣的寫法已經(jīng)使用了很久,并且并不會(huì)存在什么功能上的問題,但是將componentWillReceiveProps標(biāo)記為deprecated的原因也并不是因?yàn)楣δ軉栴},而是性能問題。

當(dāng)外部多個(gè)屬性在很短的時(shí)間間隔之內(nèi)多次變化,就會(huì)導(dǎo)致componentWillReceiveProps被多次調(diào)用。這個(gè)調(diào)用并不會(huì)被合并,如果這次內(nèi)容都會(huì)觸發(fā)異步請(qǐng)求,那么可能會(huì)導(dǎo)致多個(gè)異步請(qǐng)求阻塞。

getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.

這個(gè)生命周期函數(shù)會(huì)在每次調(diào)用render之前被觸發(fā),而讀過一點(diǎn)react源碼的童鞋都會(huì)了解,reactsetState操作是會(huì)通過transaction進(jìn)行合并的,由此導(dǎo)致的更新過程是batch的,而react中大部分的更新過程的觸發(fā)源都是setState,所以render觸發(fā)的頻率并不會(huì)非常頻繁(感謝 @leeenx20 的提醒,這里描述進(jìn)行了修改)。

在使用getDerivedStateFromProps的時(shí)候,遇到了上面說的props在很短的時(shí)間內(nèi)多次變化,也只會(huì)觸發(fā)一次render,也就是只觸發(fā)一次getDerivedStateFromProps。這樣的優(yōu)點(diǎn)不言而喻。

那么如何使用getDerivedStateFromProps進(jìn)行異步的處理呢?

If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.

官方教你怎么寫代碼系列,但是其實(shí)也沒有其他可以進(jìn)行異步操作的地方了。為了響應(yīng)props的變化,就需要在componentDidUpdate中根據(jù)新的props和state來進(jìn)行異步操作,比如從服務(wù)端拉取數(shù)據(jù)。

// 在getDerivedStateFromProps中進(jìn)行state的改變
static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.type !== prevState.type) {
        return {
            type: nextProps.type,
        };
    }
    return null;
}
// 在componentDidUpdate中進(jìn)行異步操作,驅(qū)動(dòng)數(shù)據(jù)的變化
componentDidUpdate() {
    this._loadAsyncData({...this.state});
}

getSnapshotBeforeUpdate(prevProps, prevState)

這個(gè)新更新代替componentWillUpdate。
常見的 componentWillUpdate 的用例是在組件更新前,讀取當(dāng)前某個(gè) DOM 元素的狀態(tài),并在 componentDidUpdate 中進(jìn)行相應(yīng)的處理。
這兩者的區(qū)別在于:

在 React 開啟異步渲染模式后,在 render 階段讀取到的 DOM 元素狀態(tài)并不總是和 commit 階段相同,這就導(dǎo)致在
componentDidUpdate 中使用 componentWillUpdate 中讀取到的 DOM 元素狀態(tài)是不安全的,因?yàn)檫@時(shí)的值很有可能已經(jīng)失效了。
getSnapshotBeforeUpdate 會(huì)在最終的 render 之前被調(diào)用,也就是說在 getSnapshotBeforeUpdate 中讀取到的 DOM 元素狀態(tài)是可以保證與 componentDidUpdate 中一致的。
此生命周期返回的任何值都將作為參數(shù)傳遞給componentDidUpdate()。

小結(jié)

react為了防止部分開發(fā)者濫用生命周期,可謂非常盡心盡力了。既然你用不好,我就干脆不讓你用。一個(gè)靜態(tài)的生命周期函數(shù)可以讓狀態(tài)的修改更加規(guī)范和合理。

React 16是最近一年多React更新最大的版本。推薦向下兼容的Fiber,哈哈,本人還沒仔細(xì)看,據(jù)說是防止了客戶端react在進(jìn)行渲染的時(shí)候阻塞頁面的其他交互行為。Fiber源碼速覽

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 作為一個(gè)合格的開發(fā)者,不要只滿足于編寫了可以運(yùn)行的代碼。而要了解代碼背后的工作原理;不要只滿足于自己的程序...
    六個(gè)周閱讀 8,674評(píng)論 1 33
  • React 生命周期很多人都了解,但通常我們所了解的都是單個(gè)組件的生命周期,但針對(duì)Hooks 組件、多個(gè)關(guān)聯(lián)組件(...
    前端js閱讀 7,287評(píng)論 3 7
  • tips:很久沒在簡(jiǎn)書更新文章了,歡迎大家逛逛我在github的博客點(diǎn)擊查看 。 React v16.0前的生命周...
    aermin閱讀 218,993評(píng)論 13 169
  • 起步 安裝官方腳手架: npm install -g create-react-app 創(chuàng)建項(xiàng)目: create-...
    Twoold閱讀 1,554評(píng)論 0 0
  • 夢(mèng)醒時(shí)分微風(fēng)吹,夏日曦陽灼床邊。 鳥鳴驚醒夢(mèng)中人,蟬聲如雷甚心中。 哀嘆夢(mèng)魘已逝去,我心無成深愜意。 愿作水舟乘風(fēng)...
    人生若不苦何以為甘甜閱讀 249評(píng)論 0 0

友情鏈接更多精彩內(nèi)容