聊聊setState的用法,你會幾個?
本文轉載自微信公眾號「前端UpUp」,作者前端UpUp。轉載本文請聯系前端UpUp公眾號。
setState是同步還是異步?
首先,這個問題的拋出,我會想為什么要拋出這個問題呢?如果說,你需要依賴狀態更新后的值時,那么首先如何做呢?
- 對于Class Component而言,我們可以在componentDidMount或者是componentDidUpdate階段來執行。
- 對于Function Component而言,我們可以在useEffect的回調函數中執行。
首先,我們先給出結論,在React中不同的模式它的情況是不一樣的,主要拿兩種模式來說。
- legacy模式
- concurrent模式
legacy 模式
這是當前 React app 使用的方式??
- ReactDOM.render(<App />, rootNode)
當前沒有計劃刪除本模式,但是這個模式可能不支持這些新功能。
回到我們上述的問題,setState是同步的還是異步的?
- 當在legacy模式下,命中batchedUpdates時,setState是異步的。
- 當在legacy模式下,沒命中batchedUpdates時,setState是同步的。
既然聊到了這里,我們來說一說batchedUpdates函數的作用。
那么它是干嘛的呢?如果你在處理邏輯函數中多次調用this.setState時,它是如何更新狀態的呢?
- this.setState({
- value: this.state.value + 1
- })
- this.setState({
- value: this.state.value + 1
- })
- this.setState({
- value: this.state.value + 1
- })
那React實現了這個批量更新的操作,將多次的setState合并為一次更新,那么它是如何實現的呢?batchedUpdates函數就登場了。
- function batchedUpdates$1(fn, a) {
- var prevExecutionContext = executionContext;
- executionContext |= BatchedContext;
- try {
- return fn(a);
- } finally {
- executionContext = prevExecutionContext;
- if (executionContext === NoContext) {
- // Flush the immediate callbacks that were scheduled during this batch
- resetRenderTimer();
- flushSyncCallbackQueue(); // 同步的更新
- }
- }
- }
這個函數會傳遞一個fn,當執行fn之前,會在executionContext變量上附加一個BatchedContext,當fn執行完畢后,executionContext就會把之前的BatchedContext標記給去除掉。
- 這樣子一來,當executionContext帶上了BatchedContext標記的話,react內部就會去做判斷,帶上了這個標記,這次的更新就是批處理,那么此次更新就是異步的。
那么,我們是不是能夠假設一下,如果在執行完fn函數后,再去更新狀態的話,是不是就能完成同步的更新呢?
setTimeout函數,我們可以把setState放在定時器中,這樣子一來的話,當fn函數執行完時,BatchedContext標記也去掉了,然后等到 setTimeout 的回調函數等到空閑被執行的時候,才會執行 setState。
- setTimeout(() => {
- this.setState({ value: this.state.value + 1})
- }, 0)
這也就是當executionContext === NoContext,也就是會執行flushSyncCallbackQueue函數,完成此次的同步更新。
當然了,在concurrent 模式下,又是有所不同的。
這個時候,我們得談一談scheduleUpdateOnFiber函數。
我們都知道任務調度的起點是 scheduleUpdateOnFiber 方法,React.render、setState、forceUpdate、React Hooks 的dispatchAction 都會經過 scheduleUpdateOnFiber。
- function scheduleUpdateOnFiber(fiber, lane, eventTime) {
- // ...
- if (root === workInProgressRoot) {
- // ......
- } // TO an argument to that function and this one.
- if (lane === SyncLane) { // 同步任務
- if ( // 檢查當前是不是在unbatchedUpdates(非批量更新),(初次渲染的ReactDOM.render就是unbatchedUpdates)
- (executionContext & LegacyUnbatchedContext) !== NoContext && // Check if we're not already rendering
- (executionContext & (RenderContext | CommitContext)) === NoContext) {
- // Register pending interactions on the root to avoid losing traced interaction data.
- schedulePendingInteractions(root, lane);
- performSyncWorkOnRoot(root);
- } else {
- ensureRootIsScheduled(root, eventTime);
- schedulePendingInteractions(root, lane);
- if (executionContext === NoContext) {
- resetRenderTimer();
- flushSyncCallbackQueue();
- }
- }
- } else { // 異步任務
- // concurrent模式下是跳過了 flushSyncCallbackQueue 同步更新
- // ....
- }
- }
scheduleUpdateOnFiber函數通過lane === SyncLane來判斷是同步任務還是異步任務,我們通過ReactDom.render()方式創建的React app是會進入這個判斷的,而concurrent模式下,則不同,那么它是如何創建的呢??
concurrent 模式
你可以理解成,這個暫時還是實驗階段,當未來穩定后,將會作為React開發的默認開發模式,它是如何創建一個React App應用的呢??
- ReactDOM.createRoot(rootNode).render(<App />)
這個模式開啟了所有的新功能。
concurrent模式下狀態的更新都是異步的。
關于React的concurrent 模式解讀,有興趣可以看看官方文檔。
到這里的話,似乎我們對React中setState是同步的還是異步的就有所了解了。
- 哪些會命中batchUpdate機制
- 生命周期(和它調用函數)
- React中注冊的事件
- React可以'管理入口'