由實(shí)際問題探究setState的執(zhí)行機(jī)制

一.幾個(gè)開發(fā)中經(jīng)常會(huì)遇到的問題

以下幾個(gè)問題是我們?cè)趯?shí)際開發(fā)中經(jīng)常會(huì)遇到的場景,下面用幾個(gè)簡單的示例代碼來還原一下。

1.setState是同步還是異步的,為什么有的時(shí)候不能立即拿到更新結(jié)果而有的時(shí)候可以?

1.1 鉤子函數(shù)和React合成事件中的setState

現(xiàn)在有兩個(gè)組件

?componentDidMount() {

? ?console.log('parent componentDidMount');

?}

?render() {

? ?return (

? ? ?<div>

? ? ? ?<SetState2></SetState2>

? ? ? ?<SetState></SetState>

? ? ?</div>

? ?);

?}

組件內(nèi)部放入同樣的代碼,并在Setstate1中的componentDidMount中放入一段同步延時(shí)代碼,打印延時(shí)時(shí)間:

?componentWillUpdate() {

? ?console.log('componentWillUpdate');

?}

?componentDidUpdate() {

? ?console.log('componentDidUpdate');

?}

?componentDidMount() {

? ?console.log('SetState調(diào)用setState');

? ?this.setState({

? ? ?index: this.state.index + 1

? ?})

? ?console.log('state', this.state.index);

? ?console.log('SetState調(diào)用setState');

? ?this.setState({

? ? ?index: this.state.index + 1

? ?})

? ?console.log('state', this.state.index);

?}

下面是執(zhí)行結(jié)果:

說明:

1.調(diào)用setState不會(huì)立即更新

2.所有組件使用的是同一套更新機(jī)制,當(dāng)所有組件didmount后,父組件didmount,然后執(zhí)行更新

3.更新時(shí)會(huì)把每個(gè)組件的更新合并,每個(gè)組件只會(huì)觸發(fā)一次更新的生命周期。

1.2 異步函數(shù)和原生事件中的setstate?

在setTimeout中調(diào)用setState(例子和在瀏覽器原生事件以及接口回調(diào)中執(zhí)行效果相同)

?componentDidMount() {

? ?setTimeout(() => {

? ? ?console.log('調(diào)用setState');

? ? ?this.setState({

? ? ? ?index: this.state.index + 1

? ? ?})

? ? ?console.log('state', this.state.index);

? ? ?console.log('調(diào)用setState');

? ? ?this.setState({

? ? ? ?index: this.state.index + 1

? ? ?})

? ? ?console.log('state', this.state.index);

? ?}, 0);

?}

執(zhí)行結(jié)果:

說明:

1.在父組件didmount后執(zhí)行

2.調(diào)用setState同步更新

2.為什么有時(shí)連續(xù)兩次setState只有一次生效?

分別執(zhí)行以下代碼:

?componentDidMount() {

? ?this.setState({ index: this.state.index + 1 }, () => {

? ? ?console.log(this.state.index);

? ?})

? ?this.setState({ index: this.state.index + 1 }, () => {

? ? ?console.log(this.state.index);

? ?})

?}

?componentDidMount() {

? ?this.setState((preState) => ({ index: preState.index + 1 }), () => {

? ? ?console.log(this.state.index);

? ?})

? ?this.setState(preState => ({ index: preState.index + 1 }), () => {

? ? ?console.log(this.state.index);

? ?})

?}

執(zhí)行結(jié)果:

1

1

2

2

說明:

1.直接傳遞對(duì)象的setstate會(huì)被合并成一次

2.使用函數(shù)傳遞state不會(huì)被合并

二.setState執(zhí)行過程

由于源碼比較復(fù)雜,就不貼在這里了,有興趣的可以去github上clone一份然后按照下面的流程圖去走一遍。

1.流程圖

partialState:setState傳入的第一個(gè)參數(shù),對(duì)象或函數(shù)

_pendingStateQueue:當(dāng)前組件等待執(zhí)行更新的state隊(duì)列

isBatchingUpdates:react用于標(biāo)識(shí)當(dāng)前是否處于批量更新狀態(tài),所有組件公用

dirtyComponent:當(dāng)前所有處于待更新狀態(tài)的組件隊(duì)列

transcation:react的事務(wù)機(jī)制,在被事務(wù)調(diào)用的方法外包裝n個(gè)waper對(duì)象,并一次執(zhí)行:waper.init、被調(diào)用方法、waper.close

FLUSH_BATCHED_UPDATES:用于執(zhí)行更新的waper,只有一個(gè)close方法

2.執(zhí)行過程

對(duì)照上面流程圖的文字說明,大概可分為以下幾步:

1.將setState傳入的partialState參數(shù)存儲(chǔ)在當(dāng)前組件實(shí)例的state暫存隊(duì)列中。

2.判斷當(dāng)前React是否處于批量更新狀態(tài),如果是,將當(dāng)前組件加入待更新的組件隊(duì)列中。

3.如果未處于批量更新狀態(tài),將批量更新狀態(tài)標(biāo)識(shí)設(shè)置為true,用事務(wù)再次調(diào)用前一步方法,保證當(dāng)前組件加入到了待更新組件隊(duì)列中。

4.調(diào)用事務(wù)的waper方法,遍歷待更新組件隊(duì)列依次執(zhí)行更新。

5.執(zhí)行生命周期componentWillReceiveProps。

6.將組件的state暫存隊(duì)列中的state進(jìn)行合并,獲得最終要更新的state對(duì)象,并將隊(duì)列置為空。

7.執(zhí)行生命周期componentShouldUpdate,根據(jù)返回值判斷是否要繼續(xù)更新。

8.執(zhí)行生命周期componentWillUpdate。

9.執(zhí)行真正的更新,render。

10.執(zhí)行生命周期componentDidUpdate。

三.總結(jié)

1.鉤子函數(shù)和合成事件中:

在react的生命周期和合成事件中,react仍然處于他的更新機(jī)制中,這時(shí)isBranchUpdate為true。

按照上述過程,這時(shí)無論調(diào)用多少次setState,都會(huì)不會(huì)執(zhí)行更新,而是將要更新的state存入_pendingStateQueue,將要更新的組件存入dirtyComponent。

當(dāng)上一次更新機(jī)制執(zhí)行完畢,以生命周期為例,所有組件,即最頂層組件didmount后會(huì)將isBranchUpdate設(shè)置為false。這時(shí)將執(zhí)行之前累積的setState。

2.異步函數(shù)和原生事件中

由執(zhí)行機(jī)制看,setState本身并不是異步的,而是如果在調(diào)用setState時(shí),如果react正處于更新過程,當(dāng)前更新會(huì)被暫存,等上一次更新執(zhí)行后在執(zhí)行,這個(gè)過程給人一種異步的假象。

在生命周期,根據(jù)JS的異步機(jī)制,會(huì)將異步函數(shù)先暫存,等所有同步代碼執(zhí)行完畢后在執(zhí)行,這時(shí)上一次更新過程已經(jīng)執(zhí)行完畢,isBranchUpdate被設(shè)置為false,根據(jù)上面的流程,這時(shí)再調(diào)用setState即可立即執(zhí)行更新,拿到更新結(jié)果。

3.partialState合并機(jī)制

我們看下流程中_processPendingState的代碼,這個(gè)函數(shù)是用來合并state暫存隊(duì)列的,最后返回一個(gè)合并后的state。

?_processPendingState: function (props, context) {

? ?var inst = this._instance;

? ?var queue = this._pendingStateQueue;

? ?var replace = this._pendingReplaceState;

? ?this._pendingReplaceState = false;

? ?this._pendingStateQueue = null;

? ?if (!queue) {

? ? ?return inst.state;

? ?}

? ?if (replace && queue.length === 1) {

? ? ?return queue[0];

? ?}

? ?var nextState = _assign({}, replace ? queue[0] : inst.state);

? ?for (var i = replace ? 1 : 0; i < queue.length; i++) {

? ? ?var partial = queue[i];

? ? ?_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);

? ?}

? ?return nextState;

?},

我們只需要關(guān)注下面這段代碼:

_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);

如果傳入的是對(duì)象,很明顯會(huì)被合并成一次:

Object.assign(

?nextState,

?{index: state.index+ 1},

?{index: state.index+ 1}

)

如果傳入的是函數(shù),函數(shù)的參數(shù)preState是前一次合并后的結(jié)果,所以計(jì)算結(jié)果是準(zhǔn)確的。

4.componentDidMount調(diào)用setstate

在componentDidMount()中,你 可以立即調(diào)用setState()。它將會(huì)觸發(fā)一次額外的渲染,但是它將在瀏覽器刷新屏幕之前發(fā)生。這保證了在此情況下即使render()將會(huì)調(diào)用兩次,用戶也不會(huì)看到中間狀態(tài)。謹(jǐn)慎使用這一模式,因?yàn)樗?dǎo)致性能問題。在大多數(shù)情況下,你可以 在constructor()中使用賦值初始狀態(tài)來代替。然而,有些情況下必須這樣,比如像模態(tài)框和工具提示框。這時(shí),你需要先測(cè)量這些DOM節(jié)點(diǎn),才能渲染依賴尺寸或者位置的某些東西。

以上是官方文檔的說明,不推薦直接在componentDidMount直接調(diào)用setState,由上面的分析:componentDidMount本身處于一次更新中,我們又調(diào)用了一次setState,就會(huì)在未來再進(jìn)行一次render,造成不必要的性能浪費(fèi),大多數(shù)情況可以設(shè)置初始值來搞定。

當(dāng)然在componentDidMount我們可以調(diào)用接口,再回調(diào)中去修改state,這是正確的做法。

當(dāng)state初始值依賴dom屬性時(shí),在componentDidMount中setState是無法避免的。

5.componentWillUpdatecomponentDidUpdate

這兩個(gè)生命周期中不能調(diào)用setState。

由上面的流程圖很容易發(fā)現(xiàn),在它們里面調(diào)用setState會(huì)造成死循環(huán),導(dǎo)致程序崩潰。

6.推薦使用方式

在調(diào)用setState時(shí)使用函數(shù)傳遞state值,在回調(diào)函數(shù)中獲取最新更新后的state。

?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 作為一個(gè)合格的開發(fā)者,不要只滿足于編寫了可以運(yùn)行的代碼。而要了解代碼背后的工作原理;不要只滿足于自己的程序...
    六個(gè)周閱讀 8,664評(píng)論 1 33
  • 起步 安裝官方腳手架: npm install -g create-react-app 創(chuàng)建項(xiàng)目: create-...
    Twoold閱讀 1,553評(píng)論 0 0
  • 在react中,通過管理狀態(tài)來實(shí)現(xiàn)對(duì)組件的管理,通過this.state()來訪問state,通過this.set...
    青艸止閱讀 1,264評(píng)論 0 1
  • 40、React 什么是React?React 是一個(gè)用于構(gòu)建用戶界面的框架(采用的是MVC模式):集中處理VIE...
    萌妹撒閱讀 1,179評(píng)論 0 1
  • React一些特點(diǎn) 專注于視圖層 業(yè)務(wù)框架(也可以理解為model層)可以根據(jù)業(yè)務(wù)場景自行選擇。 Virtual ...
    vam笙閱讀 212評(píng)論 0 0

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