- setState 是同步更新還是異步更新?
- 多次調用 setState 函數,React 會不會進行合并操作?
首先是第一個問題,答:
setState
有時是同步更新的,而有時卻是異步更新。
一般情況下,
setState
基本是異步更新,例如:
下面的例子是
setState
同步更新的例子:
把
setState
放在定時器裡就會同步更新。放在自定義事件函數裡也會同步更新,例如:
如果不使用定時器或者添加自定義函數獲得更新後的狀态,可以給
setState
函數傳入第二個參數,該參數是一個函數,它會在 state 更新完成後調用。
更新合并
一般情況下,多次調用
setState
函數 React 會把 state 對象合并到目前的 state。例如:
雖然多次調用了
setState
,但是點選按鈕時 count 隻增加 1。如果不想合并操作,可以把這些操作放在定時器當中,或者自定義事件當中。或者給
setState
的第一個參數傳入函數,例如:
下圖是
setState
調用時的大緻流程。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SZxIWN5MTYmhTY1AzMxEWN0YTNjlTOwMmMklzNhZmNx8CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
圖中如果條件是 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
的值更新後執行回調函數。