新四季網

react接收的是什麼數據(React的setState)

2023-05-03 06:49:53 2

react接收的是什麼數據?setState 是同步還是異步?肯定是異步的呀,接下來我們就來聊聊關於react接收的是什麼數據?以下內容大家不妨參考一二希望能幫到您!

react接收的是什麼數據

setState 是同步還是異步?

肯定是異步的呀。

確定麼?那看一下這段代碼會列印什麼:

import { Component } from 'react';

class Dong extends Component {

constructor {

super;

this.state = {

count: 0

}

}

componentDidMount {

setTimeout( => {

this.setState({

count: 1

});

console.log(this.state.count);

this.setState({

count: 2

});

console.log(this.state.count);

});

}

render {

console.log('render:', this.state.count);

return {this.state.count};

}

}

在 setTimeout 裡修改了兩次 state,並列印了 state 的值。

如果是異步的,那應該列印的時候 count 還沒修改,依然是 0,所以列印兩次 0。

然後初始化渲染一次,setState 後再渲染一次,應該 render 兩次,count 分別為 0 和 2。

按照異步的方式來分析,確實應該是這樣的。

我們執行一下:

圖片

會發現兩次列印分別是 1 和 2,也就是說 setState 同步修改了 state,然後每次都觸發了渲染,所以一共 render 3 次,分別是 0、1、2。

那這麼說 setState 是同步的?

確定麼?那看下這段代碼會列印什麼?

class Dong extends Component {

constructor {

super;

this.state = {

count: 0

}

}

componentDidMount {

this.setState({

count: 1

});

console.log(this.state.count);

this.setState({

count: 2

});

console.log(this.state.count);

this.setState({

count: 3

});

console.log(this.state.count);

}

render {

console.log('render:', this.state.count);

return {this.state.count};

}

}

如果 setState 是同步的,那執行完就會修改 state,應該分別列印 1、2、3,然後觸發三次 render,加上最開始的一次,一共四次,列印 0、1、2、3。

我們來執行一下:

圖片

三次列印都是 0,這說明 setState 是異步的。而且三次 setState 只觸發了一次 render,加上最開始的 render,一共兩次,列印 0、3。

什麼鬼,怎麼又是異步的了?

而且不止 class 組件的 setState 是這樣,換成 function 組件的 useState 也是一樣的:

比如修改三次 state,只會 render 一次:

圖片

而在 setTimeout 裡,每次修改 state 都會 render:

圖片

是不是有點暈,什麼情況下 setState 是同步的,什麼情況下是異步的呢?

這要從源碼找答案了,我們來讀一下 setState 的源碼。

首先理一下 React 渲染的流程:

React 渲染流程

react 通過 jsx 來描述界面,jsx 可以通過 babel 等編譯器編譯成 render function,然後執行後產生 vdom:

圖片

這個 vdom 也不是直接渲染的,而是會先轉化為 fiber,之後再渲染。

因為 vdom 裡每個節點只記錄了子節點(children),沒有記錄兄弟節點,所以必須一次性渲染完,不能打斷。

而轉成 fiber 的鍊表結構就會記錄父節點(return)、子節點(child)、兄弟節點(sibling),就變成了可打斷的。

圖片

這裡的 vdom 是 React Element 對象:

圖片

轉化為 fiber 之後是 FiberNode 的對象:

圖片

從 vdom 轉換成 fiber 的過程就叫做 reconcile,轉換過程中會順便創建對應的 dom 元素,然後全部轉換完後一次性 commit 到 dom。

這個過程不是一次性的,是通過 scheduler 調度執行的,那也就可以分批次進行,也就是可打斷的含義。

這就是 React 的 fiber 架構下的渲染流程。

理論說完了,我們來對應到源碼看一下(這裡看的是 v17 的源碼):

react 把 schedule 和 reconcile 叫做 render 階段,這個階段就是把 vdom 轉為 fiber。(schedule 只是讓 reconcile 可以分多次執行,可以打斷,但做的事情是不變的,所以 schedule 也是 render 階段的一部分)

之後把 fiber 更新到 dom 的過程就叫做 commit 階段。

對應到源碼裡就是這樣的:

圖片

這個 performSyncWorkOnRoot 就是渲染的入口,就像之前所說的,會先執行 render 階段,把 vdom 轉成 fbier,然後再執行 commit,更新到 dom。

render 階段會執行一個調度的 loop:

圖片

這個 loop 就是不斷地處理一個個 fiber 的 reconcile:

圖片

每個節點都有 beginWork 和 completeWork 兩個階段,因為要做 vdom 轉 fiber,而 vdom 是一個樹形結構,需要遞歸處理:

圖片

具體不同節點的 reconcile 邏輯不同:

圖片

比如函數組件會被調用,拿到 render 出的 vdom 繼續進行 reconcile:

圖片

比如 class 組件會創建實例,調用 render 方法,拿到 vdom,然後再繼續 renconcileChildren。

圖片

總之,vdom 轉 fiber 是一個遞歸進行的過程。

之後再進行 commit 階段。

整個渲染流程的入口就是 performSyncWorkOnRoot 函數。

渲染的流程講完了,接下來就是 setState 怎麼觸發渲染的流程了:

setState 的流程

我們知道了渲染的入口就是 performSyncWorkOnRoot 函數,那 setState 修改完狀態,觸發一下這個函數不就行了?

確實是這樣的。setState 會調用 dispathAction,創建一個 update 對象放到 fiber 節點的 updateQueue 上,然後調度渲染:

圖片

調度更新自然就是調度上面說的那個 performSyncWorkOnRoot 函數:

react 會先從觸發 update 的 fiber 往上找到根 fiber 節點,然後再調用 performSyncWorkOnRoot 的函數進行渲染:

圖片

這就是 setState 之後觸發重新渲染的實現。

而 setState 是同步還是異步,也就是在這一段控制的。

我們看到判斷條件裡有個 excutionContext,這個是用來標識當前環境的,比如是批量還是非批量,是否執行過 render 階段、commit 階段。

其實在 ReactDOM.render 執行的時候會先調用 unbatchUpdates 函數:

圖片

這個函數會在 excutionContext 中設置一個 unbatach 的 flag:

圖片

這樣在 update 的時候,就會立刻執行 performSyncWorkOnRoot 來渲染。因為首次渲染的時候是馬上就要渲染的,沒必要調度。

圖片

之後走到 commit 階段會設置一個 commit 的 flag:

圖片

然後再次 setState 就不會走到 unbatch 的分支了。

那為什麼 setTimeout 裡面的 setState 會同步執行呢?

因為直接從 setTimeout 執行的異步代碼是沒有設置 excutionContext 的,那就會走到 NoContext 的分支,會立刻渲染。(這裡的 flush 最終會調用 performSyncWorkOnRoot 函數來渲染):

圖片

有什麼辦法能讓 setTimeout 裡執行的函數也有 excutionContext 呢?

其實 react17 暴露了 batchUpdates 的 api,用它包裹下,裡面的 setState 就會批量執行了:

圖片

它的源碼其實就是設置了下 excutionContext:

圖片

這樣等 setState 全部執行完之後再 flush,調用 peformSyncWorkOnRoot 渲染,效果就是批量的 setState 了。

其實按理來說 setState 不能叫異步,還是在同一個調用棧執行的,只不過順序不同而已。只能叫批量還是非批量。

在 react17 中是這麼處理的,如果是 react18,使用 createRoot 的 api 的話,就不會有這種問題了,就算是 setTimeout 裡的代碼也能批量執行,

圖片

而且為了兼容 react17 這種情況,還做了特殊處理,當沒有開啟併發模式,也就是還是用 ReactDOM.render 的 api 時,沒有指定 excutionContext 還會立刻渲染:

圖片

等 react 18 普及以後,所有的 setState 都是批量的了,就不會再有批量還是非批量的問題。

總結

雖然我們討論的是 setState 的同步異步,但這個不是 setTimeout、Promise 那種異步,只是指 setState 之後是否 state 馬上變了,是否馬上 render。

我們梳理了下 React 的渲染流程,包括 render 階段、commit 階段,render 階段是從 vdom 轉 fiber,包含 schedule 和 reconcile,commit 階段是把 fiber 更新到 dom。渲染流程的入口是 performSyncWorkOnRoot 函數。

setState 會創建 update 對象掛到 fiber 對象上,然後調度 performSyncWorkOnRoot 重新渲染。

在 react17 中,setState 是批量執行的,因為執行前會設置 executionContext。但如果在 setTimeout、事件監聽器等函數裡,就不會設置 executionContext 了,這時候 setState 會同步執行。可以在外面包一層 batchUpdates 函數,手動設置下 excutionContext 來切換成異步批量執行。

在 react18 裡面,如果用 createRoot 的 api,就不會有這種問題了。

setState 是同步還是異步這個問題等 react18 普及以後就不會再有了,因為所有的 setState 都是異步批量執行了

,
同类文章
葬禮的夢想

葬禮的夢想

夢見葬禮,我得到了這個夢想,五個要素的五個要素,水火只好,主要名字在外面,職業生涯良好,一切都應該對待他人治療誠意,由於小,吉利的冬天夢想,秋天的夢是不吉利的
找到手機是什麼意思?

找到手機是什麼意思?

找到手機是什麼意思?五次選舉的五個要素是兩名士兵的跡象。與他溝通很好。這是非常財富,它擅長運作,職業是仙人的標誌。單身男人有這個夢想,主要生活可以有人幫忙
我不怎麼想?

我不怎麼想?

我做了什麼意味著看到米飯烹飪?我得到了這個夢想,五線的主要土壤,但是Tu Ke水是錢的跡象,職業生涯更加真誠。他真誠地誠實。這是豐富的,這是夏瑞的巨星
夢想你的意思是什麼?

夢想你的意思是什麼?

你是什​​麼意思夢想的夢想?夢想,主要木材的五個要素,水的跡象,主營業務,主營業務,案子應該抓住魅力,不能疏忽,春天夢想的吉利夢想夏天的夢想不幸。詢問學者夢想
拯救夢想

拯救夢想

拯救夢想什麼意思?你夢想著拯救人嗎?拯救人們的夢想有一個現實,也有夢想的主觀想像力,請參閱週宮官方網站拯救人民夢想的詳細解釋。夢想著敵人被拯救出來
2022愛方向和生日是在[質量個性]中

2022愛方向和生日是在[質量個性]中

[救生員]有人說,在出生88天之前,胎兒已經知道哪天的出生,如何有優質的個性,將走在什麼樣的愛情之旅,將與生活生活有什么生活。今天
夢想切割剪裁

夢想切割剪裁

夢想切割剪裁什麼意思?你夢想切你的手是好的嗎?夢想切割手工切割手有一個真正的影響和反應,也有夢想的主觀想像力。請參閱官方網站夢想的細節,以削減手
夢想著親人死了

夢想著親人死了

夢想著親人死了什麼意思?你夢想夢想你的親人死嗎?夢想有一個現實的影響和反應,還有夢想的主觀想像力,請參閱夢想世界夢想死亡的親屬的詳細解釋
夢想搶劫

夢想搶劫

夢想搶劫什麼意思?你夢想搶劫嗎?夢想著搶劫有一個現實的影響和反應,也有夢想的主觀想像力,請參閱週恭吉夢官方網站的詳細解釋。夢想搶劫
夢想缺乏缺乏紊亂

夢想缺乏缺乏紊亂

夢想缺乏缺乏紊亂什麼意思?你夢想缺乏異常藥物嗎?夢想缺乏現實世界的影響和現實,還有夢想的主觀想像,請看官方網站的夢想組織缺乏異常藥物。我覺得有些東西缺失了