天天看點

回調函數是同步還是異步_React setState 是異步執行還是同步執行?

  1. setState 是同步更新還是異步更新?
  2. 多次調用 setState 函數,React 會不會進行合并操作?

首先是第一個問題,答:

setState

有時是同步更新的,而有時卻是異步更新。

一般情況下,

setState

基本是異步更新,例如:

下面的例子是

setState

同步更新的例子:

setState

放在定時器裡就會同步更新。放在自定義事件函數裡也會同步更新,例如:

如果不使用定時器或者添加自定義函數獲得更新後的狀态,可以給

setState

函數傳入第二個參數,該參數是一個函數,它會在 state 更新完成後調用。

更新合并

一般情況下,多次調用

setState

函數 React 會把 state 對象合并到目前的 state。例如:

雖然多次調用了

setState

,但是點選按鈕時 count 隻增加 1。如果不想合并操作,可以把這些操作放在定時器當中,或者自定義事件當中。或者給

setState

的第一個參數傳入函數,例如:

下圖是

setState

調用時的大緻流程。

回調函數是同步還是異步_React setState 是異步執行還是同步執行?

圖中如果條件是 true,則元件會異步更新,而如果是

false

,則會同步更新。是否處于 batchUpdate,可以用一個例子來解釋,比如下面的代碼:

isBatchingUpdates

的初始值是

true

,當沒有定時器時調用

setState

時該值還是 true,就會異步執行,而

setState

用定時器包裹後,定時器回調還沒執行

isBatchingUpdates

就變成了

false

,setState 就會同步執行。

props 或者 state 變化時,會調用

UpdateComponent

更新 props 和 state,然後調用 render 函數,生成新的虛拟節點(Vnode),然後 patch(oldVnode, newVnode),老的虛拟 DOM 和新的虛拟 DOM 對比更新視圖。

patch

分為兩個階段:

  • reconciliation 階段:執行 diff 算法(純 JS 計算);
  • commit 階段:将 diff 結果渲染到 DOM 上。

由于 JavaScript 是單線程,且和 DOM 渲染共用一個線程,當元件足夠複雜時,元件更新時計算量和渲染量都很大,同時再有 DOM 操作需求(比如動畫、拖拽等),這可能會導緻頁面卡頓。

React 考慮性能優化,就把 patch 分成了兩個階段,在 reconciliation 階段将任務拆分,拆分成多個子任務(commit 不能拆分,reconciliation 階段是純 JS 計算,比較好控制),當 DOM 渲染時就暫停任務,浏覽器空閑時再恢複計算。通過 window.requestIdleCallback API 可以讓一些任務在浏覽器空閑時調用。

關于 React fiber 和 Concurrent API 可以參考這篇文章:深入剖析 React Concurrent

setState 與 useState

setState 與 useState 功能相似,在一般情況下,

useState

也會對多次調用更新函數做合并處理,例如:

如果

useState

更新 state 傳入的也是函數時,就不會對數組做合并處理,這與 setState 行為一樣。

useState

setState

不同的是:

state

是對象時,

setState

會自動合并對象,而

useState

不會。例如:

運作時會發現,

color

并不會丢失。而如果使用

useState

,隻更新

count

,當點選按鈕一次之後

color

就會丢失,例如:

如果不讓

color

丢失,可以使用這種方式:

setState

可以同步更新,比如在外層包裹定時器,傳入第二個回調參數可以拿到更新後的資料。但

useState

是行不通的,它是異步更新,要想及時拿到更新後的資料,就需要借助

useEffect

useEffect

在首次渲染完成後執行一次,之後會在

data.count

的值更新後執行回調函數。