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

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

以下幾個問題是我們在實際開發(fā)中經(jīng)常會遇到的場景家卖,下面用幾個簡單的示例代碼來還原一下罢荡。

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

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

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

?componentDidMount() {

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

?}

?render() {

? ?return (

? ? ?<div>

? ? ? ?<SetState2></SetState2>

? ? ? ?<SetState></SetState>

? ? ?</div>

? ?);

?}

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

?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不會立即更新

2.所有組件使用的是同一套更新機制摘悴,當所有組件didmount后峭梳,父組件didmount,然后執(zhí)行更新

3.更新時會把每個組件的更新合并蹂喻,每個組件只會觸發(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.為什么有時連續(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.直接傳遞對象的setstate會被合并成一次

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

二.setState執(zhí)行過程

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

1.流程圖

partialState:setState傳入的第一個參數(shù)治笨,對象或函數(shù)

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

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

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

transcation:react的事務機制赤嚼,在被事務調(diào)用的方法外包裝n個waper對象大磺,并一次執(zhí)行:waper.init、被調(diào)用方法探膊、waper.close

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

2.執(zhí)行過程

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

1.將setState傳入的partialState參數(shù)存儲在當前組件實例的state暫存隊列中逞壁。

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

3.如果未處于批量更新狀態(tài)绳瘟,將批量更新狀態(tài)標識設置為true,用事務再次調(diào)用前一步方法姿骏,保證當前組件加入到了待更新組件隊列中糖声。

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

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

6.將組件的state暫存隊列中的state進行合并琉苇,獲得最終要更新的state對象,并將隊列置為空悦施。

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

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

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

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

三.總結(jié)

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

在react的生命周期和合成事件中肴熏,react仍然處于他的更新機制中,這時isBranchUpdate為true顷窒。

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

當上一次更新機制執(zhí)行完畢坯辩,以生命周期為例馁龟,所有組件,即最頂層組件didmount后會將isBranchUpdate設置為false漆魔。這時將執(zhí)行之前累積的setState坷檩。

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

由執(zhí)行機制看,setState本身并不是異步的改抡,而是如果在調(diào)用setState時矢炼,如果react正處于更新過程,當前更新會被暫存阿纤,等上一次更新執(zhí)行后在執(zhí)行句灌,這個過程給人一種異步的假象。

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

3.partialState合并機制

我們看下流程中_processPendingState的代碼,這個函數(shù)是用來合并state暫存隊列的叽唱,最后返回一個合并后的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);

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

Object.assign(

?nextState,

?{index: state.index+ 1},

?{index: state.index+ 1}

)

如果傳入的是函數(shù)尔觉,函數(shù)的參數(shù)preState是前一次合并后的結(jié)果凉袱,所以計算結(jié)果是準確的芥吟。

4.componentDidMount調(diào)用setstate

在componentDidMount()中侦铜,你 可以立即調(diào)用setState()。它將會觸發(fā)一次額外的渲染钟鸵,但是它將在瀏覽器刷新屏幕之前發(fā)生钉稍。這保證了在此情況下即使render()將會調(diào)用兩次,用戶也不會看到中間狀態(tài)棺耍。謹慎使用這一模式贡未,因為它常導致性能問題。在大多數(shù)情況下蒙袍,你可以 在constructor()中使用賦值初始狀態(tài)來代替俊卤。然而,有些情況下必須這樣害幅,比如像模態(tài)框和工具提示框消恍。這時,你需要先測量這些DOM節(jié)點以现,才能渲染依賴尺寸或者位置的某些東西狠怨。

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

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

當state初始值依賴dom屬性時彬碱,在componentDidMount中setState是無法避免的豆胸。

5.componentWillUpdatecomponentDidUpdate

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

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

6.推薦使用方式

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瓷患,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子遣妥,更是在濱河造成了極大的恐慌擅编,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箫踩,死亡現(xiàn)場離奇詭異爱态,居然都是意外死亡,警方通過查閱死者的電腦和手機境钟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門锦担,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慨削,你說我怎么就攤上這事洞渔。” “怎么了缚态?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵磁椒,是天一觀的道長。 經(jīng)常有香客問我玫芦,道長浆熔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任姨俩,我火速辦了婚禮蘸拔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘环葵。我一直安慰自己调窍,他們只是感情好,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布张遭。 她就那樣靜靜地躺著邓萨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪菊卷。 梳的紋絲不亂的頭發(fā)上缔恳,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機與錄音洁闰,去河邊找鬼歉甚。 笑死,一個胖子當著我的面吹牛扑眉,可吹牛的內(nèi)容都是我干的纸泄。 我是一名探鬼主播赖钞,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼聘裁!你這毒婦竟也來了雪营?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤衡便,失蹤者是張志新(化名)和其女友劉穎献起,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镣陕,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡谴餐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了茁彭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片总寒。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡扶歪,死狀恐怖理肺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情善镰,我是刑警寧澤妹萨,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站炫欺,受9級特大地震影響乎完,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜品洛,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一树姨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桥状,春花似錦帽揪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至士飒,卻和暖如春查邢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酵幕。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工扰藕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芳撒。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓邓深,卻偏偏與公主長得像他嫡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子庐完,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容

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