成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

React 架構的演變 - 從同步到異步

開發 架構
寫這篇文章的目的,主要是想弄懂 React 最新的 fiber 架構到底是什么東西,但是看了網上的很多文章,要不模棱兩可,要不就是一頓復制粘貼,根本看不懂,于是開始認真鉆研源碼。

[[343617]]

寫這篇文章的目的,主要是想弄懂 React 最新的 fiber 架構到底是什么東西,但是看了網上的很多文章,要不模棱兩可,要不就是一頓復制粘貼,根本看不懂,于是開始認真鉆研源碼。鉆研過程中,發現我想得太簡單了,React 源碼的復雜程度遠超我的想象,于是打算分幾個模塊了剖析,今天先講一講 React 的更新策略從同步變為異步的演變過程。

從 setState 說起

React 16 之所以要進行一次大重構,是因為 React 之前的版本有一些不可避免的缺陷,一些更新操作,需要由同步改成異步。所以我們先聊聊 React 15 是如何進行一次 setState 的。

  1. import React from 'react'
  2.  
  3. class App extends React.Component { 
  4.   state = { val: 0 } 
  5.   componentDidMount() { 
  6.     // 第一次調用 
  7.     this.setState({ val: this.state.val + 1 }); 
  8.     console.log('first setState', this.state); 
  9.  
  10.     // 第二次調用 
  11.     this.setState({ val: this.state.val + 1 }); 
  12.     console.log('second setState', this.state); 
  13.  
  14.     // 第三次調用 
  15.     this.setState({ val: this.state.val + 1 }, () => { 
  16.       console.log('in callback', this.state) 
  17.     }); 
  18.   } 
  19.   render() { 
  20.     return <div> val: { this.state.val } </div> 
  21.   } 
  22.  
  23. export default App; 
val: { this.state.val }

}}export default App;

 

熟悉 React 的同學應該知道,在 React 的生命周期內,多次 setState 會被合并成一次,這里雖然連續進行了三次 setState,state.val 的值實際上只重新計算了一次。

render結果

每次 setState 之后,立即獲取 state 會發現并沒有更新,只有在 setState 的回調函數內才能拿到最新的結果,這點通過我們在控制臺輸出的結果就可以證實。

控制臺輸出

網上有很多文章稱 setState 是『異步操作』,所以導致 setState 之后并不能獲取到最新值,其實這個觀點是錯誤的。setState 是一次同步操作,只是每次操作之后并沒有立即執行,而是將 setState 進行了緩存,mount 流程結束或事件操作結束,才會拿出所有的 state 進行一次計算。如果 setState 脫離了 React 的生命周期或者 React 提供的事件流,setState 之后就能立即拿到結果。

我們修改上面的代碼,將 setState 放入 setTimeout 中,在下一個任務隊列進行執行。

  1. import React from 'react'
  2.  
  3. class App extends React.Component { 
  4.   state = { val: 0 } 
  5.   componentDidMount() { 
  6.     setTimeout(() => { 
  7.       // 第一次調用 
  8.       this.setState({ val: this.state.val + 1 }); 
  9.       console.log('first setState', this.state); 
  10.    
  11.       // 第二次調用 
  12.       this.setState({ val: this.state.val + 1 }); 
  13.       console.log('second setState', this.state); 
  14.     }); 
  15.   } 
  16.   render() { 
  17.     return <div> val: { this.state.val } </div> 
  18.   } 
  19.  
  20. export default App; 

可以看到,setState 之后就能立即看到state.val 的值發生了變化。

控制臺輸出

為了更加深入理解 setState,下面簡單講解一下React 15 中 setState 的更新邏輯,下面的代碼是對源碼的一些精簡,并非完整邏輯。

舊版本 setState 源碼分析

setState 的主要邏輯都在 ReactUpdateQueue 中實現,在調用 setState 后,并沒有立即修改 state,而是將傳入的參數放到了組件內部的 _pendingStateQueue 中,之后調用 enqueueUpdate 來進行更新。

  1. // 對外暴露的 React.Component 
  2. function ReactComponent() { 
  3.   this.updater = ReactUpdateQueue; 
  4. // setState 方法掛載到原型鏈上 
  5. ReactComponent.prototype.setState = function (partialState, callback) { 
  6.   // 調用 setState 后,會調用內部的 updater.enqueueSetState 
  7.   this.updater.enqueueSetState(this, partialState); 
  8.   if (callback) { 
  9.     this.updater.enqueueCallback(this, callback, 'setState'); 
  10.   } 
  11. }; 
  12.  
  13. var ReactUpdateQueue = { 
  14.   enqueueSetState(component, partialState) { 
  15.     // 在組件的 _pendingStateQueue 上暫存新的 state 
  16.     if (!component._pendingStateQueue) { 
  17.       component._pendingStateQueue = []; 
  18.     } 
  19.     var queue = component._pendingStateQueue; 
  20.     queue.push(partialState); 
  21.     enqueueUpdate(component); 
  22.   }, 
  23.   enqueueCallback: function (component, callback, callerName) { 
  24.     // 在組件的 _pendingCallbacks 上暫存 callback 
  25.     if (component._pendingCallbacks) { 
  26.       component._pendingCallbacks.push(callback); 
  27.     } else { 
  28.       component._pendingCallbacks = [callback]; 
  29.     } 
  30.     enqueueUpdate(component); 
  31.   } 

enqueueUpdate 首先會通過 batchingStrategy.isBatchingUpdates 判斷當前是否在更新流程,如果不在更新流程,會調用 batchingStrategy.batchedUpdates() 進行更新。如果在流程中,會將待更新的組件放入 dirtyComponents 進行緩存。

  1. var dirtyComponents = []; 
  2. function enqueueUpdate(component) { 
  3.   if (!batchingStrategy.isBatchingUpdates) { 
  4.    // 開始進行批量更新 
  5.     batchingStrategy.batchedUpdates(enqueueUpdate, component); 
  6.     return
  7.   } 
  8.   // 如果在更新流程,則將組件放入臟組件隊列,表示組件待更新 
  9.   dirtyComponents.push(component); 

batchingStrategy 是 React 進行批處理的一種策略,該策略的實現基于 Transaction,雖然名字和數據庫的事務一樣,但是做的事情卻不一樣。

  1. class ReactDefaultBatchingStrategyTransaction extends Transaction { 
  2.   constructor() { 
  3.     this.reinitializeTransaction() 
  4.   } 
  5.   getTransactionWrappers () { 
  6.     return [ 
  7.       { 
  8.         initialize: () => {}, 
  9.         close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates) 
  10.       }, 
  11.       { 
  12.         initialize: () => {}, 
  13.         close: () => { 
  14.           ReactDefaultBatchingStrategy.isBatchingUpdates = false
  15.         } 
  16.       } 
  17.     ] 
  18.   } 
  19.  
  20. var transaction = new ReactDefaultBatchingStrategyTransaction(); 
  21.  
  22. var batchingStrategy = { 
  23.   // 判斷是否在更新流程中 
  24.   isBatchingUpdates: false
  25.   // 開始進行批量更新 
  26.   batchedUpdates: function (callback, component) { 
  27.     // 獲取之前的更新狀態 
  28.     var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; 
  29.   // 將更新狀態修改為 true 
  30.     ReactDefaultBatchingStrategy.isBatchingUpdates = true
  31.     if (alreadyBatchingUpdates) { 
  32.       // 如果已經在更新狀態中,等待之前的更新結束 
  33.       return callback(callback, component); 
  34.     } else { 
  35.       // 進行更新 
  36.       return transaction.perform(callback, null, component); 
  37.     } 
  38.   } 
  39. }; 

Transaction 通過 perform 方法啟動,然后通過擴展的 getTransactionWrappers 獲取一個數組,該數組內存在多個 wrapper 對象,每個對象包含兩個屬性:initialize、close。perform 中會先調用所有的 wrapper.initialize,然后調用傳入的回調,最后調用所有的 wrapper.close。

  1. class Transaction { 
  2.  reinitializeTransaction() { 
  3.     this.transactionWrappers = this.getTransactionWrappers(); 
  4.   } 
  5.  perform(method, scope, ...param) { 
  6.     this.initializeAll(0); 
  7.     var ret = method.call(scope, ...param); 
  8.     this.closeAll(0); 
  9.     return ret; 
  10.   } 
  11.  initializeAll(startIndex) { 
  12.     var transactionWrappers = this.transactionWrappers; 
  13.     for (var i = startIndex; i < transactionWrappers.length; i++) { 
  14.       var wrapper = transactionWrappers[i]; 
  15.       wrapper.initialize.call(this); 
  16.     } 
  17.   } 
  18.  closeAll(startIndex) { 
  19.     var transactionWrappers = this.transactionWrappers; 
  20.     for (var i = startIndex; i < transactionWrappers.length; i++) { 
  21.       var wrapper = transactionWrappers[i]; 
  22.       wrapper.close.call(this); 
  23.     } 
  24.   } 

transaction.perform

 

React 源代碼的注釋中,也形象的展示了這一過程。

  1. /* 
  2. *                       wrappers (injected at creation time
  3. *                                      +        + 
  4. *                                      |        | 
  5. *                    +-----------------|--------|--------------+ 
  6. *                    |                 v        |              | 
  7. *                    |      +---------------+   |              | 
  8. *                    |   +--|    wrapper1   |---|----+         | 
  9. *                    |   |  +---------------+   v    |         | 
  10. *                    |   |          +-------------+  |         | 
  11. *                    |   |     +----|   wrapper2  |--------+   | 
  12. *                    |   |     |    +-------------+  |     |   | 
  13. *                    |   |     |                     |     |   | 
  14. *                    |   v     v                     v     v   | wrapper 
  15. *                    | +---+ +---+   +---------+   +---+ +---+ | invariants 
  16. * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained 
  17. * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|--------> 
  18. *                    | |   | |   |   |         |   |   | |   | | 
  19. *                    | |   | |   |   |         |   |   | |   | | 
  20. *                    | |   | |   |   |         |   |   | |   | | 
  21. *                    | +---+ +---+   +---------+   +---+ +---+ | 
  22. *                    |  initialize                    close    | 
  23. *                    +-----------------------------------------+ 
  24. */ 

我們簡化一下代碼,再重新看一下 setState 的流程。

  1. // 1. 調用 Component.setState 
  2. ReactComponent.prototype.setState = function (partialState) { 
  3.   this.updater.enqueueSetState(this, partialState); 
  4. }; 
  5.  
  6. // 2. 調用 ReactUpdateQueue.enqueueSetState,將 state 值放到 _pendingStateQueue 進行緩存 
  7. var ReactUpdateQueue = { 
  8.   enqueueSetState(component, partialState) { 
  9.     var queue = component._pendingStateQueue || (component._pendingStateQueue = []); 
  10.     queue.push(partialState); 
  11.     enqueueUpdate(component); 
  12.   } 
  13.  
  14. // 3. 判斷是否在更新過程中,如果不在就進行更新 
  15. var dirtyComponents = []; 
  16. function enqueueUpdate(component) { 
  17.   // 如果之前沒有更新,此時的 isBatchingUpdates 肯定是 false 
  18.   if (!batchingStrategy.isBatchingUpdates) { 
  19.     // 調用 batchingStrategy.batchedUpdates 進行更新 
  20.     batchingStrategy.batchedUpdates(enqueueUpdate, component); 
  21.     return
  22.   } 
  23.   dirtyComponents.push(component); 
  24.  
  25. // 4. 進行更新,更新邏輯放入事務中進行處理 
  26. var batchingStrategy = { 
  27.   isBatchingUpdates: false
  28.   // 注意:此時的 callback 為 enqueueUpdate 
  29.   batchedUpdates: function (callback, component) { 
  30.     var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; 
  31.     ReactDefaultBatchingStrategy.isBatchingUpdates = true
  32.     if (alreadyBatchingUpdates) { 
  33.       // 如果已經在更新狀態中,重新調用 enqueueUpdate,將 component 放入 dirtyComponents 
  34.       return callback(callback, component); 
  35.     } else { 
  36.       // 進行事務操作 
  37.       return transaction.perform(callback, null, component); 
  38.     } 
  39.   } 
  40. }; 

啟動事務可以拆分成三步來看:

先執行 wrapper 的 initialize,此時的 initialize 都是一些空函數,可以直接跳過;

然后執行 callback(也就是 enqueueUpdate),執行 enqueueUpdate 時,由于已經進入了更新狀態,batchingStrategy.isBatchingUpdates 被修改成了 true,所以最后還是會把 component 放入臟組件隊列,等待更新;

后面執行的兩個 close 方法,第一個方法的 flushBatchedUpdates 是用來進行組件更新的,第二個方法用來修改更新狀態,表示更新已經結束。

  1. getTransactionWrappers () { 
  2.   return [ 
  3.     { 
  4.       initialize: () => {}, 
  5.       close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates) 
  6.     }, 
  7.     { 
  8.       initialize: () => {}, 
  9.       close: () => { 
  10.         ReactDefaultBatchingStrategy.isBatchingUpdates = false
  11.       } 
  12.     } 
  13.   ] 

flushBatchedUpdates 里面會取出所有的臟組件隊列進行 diff,最后更新到 DOM。

  1. function flushBatchedUpdates() { 
  2.   if (dirtyComponents.length) { 
  3.     runBatchedUpdates() 
  4.   } 
  5. }; 
  6.  
  7. function runBatchedUpdates() { 
  8.   // 省略了一些去重和排序的操作 
  9.   for (var i = 0; i < dirtyComponents.length; i++) { 
  10.     var component = dirtyComponents[i]; 
  11.  
  12.     // 判斷組件是否需要更新,然后進行 diff 操作,最后更新 DOM。 
  13.     ReactReconciler.performUpdateIfNecessary(component); 
  14.   } 

performUpdateIfNecessary() 會調用 Component.updateComponent(),在updateComponent() 中,會從 _pendingStateQueue 中取出所有的值來更新。

  1. // 獲取最新的 state 
  2. _processPendingState() { 
  3.   var inst = this._instance; 
  4.   var queue = this._pendingStateQueue; 
  5.  
  6.   var nextState = { ...inst.state }; 
  7.   for (var i = 0; i < queue.length; i++) { 
  8.     var partial = queue[i]; 
  9.     Object.assign( 
  10.       nextState, 
  11.       typeof partial === 'function' ? partial(inst, nextState) : partial 
  12.    ); 
  13.   } 
  14.   return nextState; 
  15. // 更新組件 
  16. updateComponent(prevParentElement, nextParentElement) { 
  17.   var inst = this._instance; 
  18.   var prevProps = prevParentElement.props; 
  19.   var nextProps = nextParentElement.props; 
  20.   var nextState = this._processPendingState(); 
  21.   var shouldUpdate =  
  22.       !shallowEqual(prevProps, nextProps) || 
  23.       !shallowEqual(inst.state, nextState); 
  24.    
  25.   if (shouldUpdate) { 
  26.     // diff 、update DOM 
  27.   } else { 
  28.     inst.props = nextProps; 
  29.     inst.state = nextState; 
  30.   } 
  31.   // 后續的操作包括判斷組件是否需要更新、diff、更新到 DOM 

setState 合并原因

按照剛剛講解的邏輯,setState 的時候,batchingStrategy.isBatchingUpdates 為 false 會開啟一個事務,將組件放入臟組件隊列,最后進行更新操作,而且這里都是同步操作。講道理,setState 之后,我們可以立即拿到最新的 state。

然而,事實并非如此,在 React 的生命周期及其事件流中,batchingStrategy.isBatchingUpdates 的值早就被修改成了 true。可以看看下面兩張圖:

Mount

事件調用

在組件 mount 和事件調用的時候,都會調用 batchedUpdates,這個時候已經開始了事務,所以只要不脫離 React,不管多少次 setState 都會把其組件放入臟組件隊列等待更新。一旦脫離 React 的管理,比如在 setTimeout 中,setState 立馬變成單打獨斗。

Concurrent 模式

React 16 引入的 Fiber 架構,就是為了后續的異步渲染能力做鋪墊,雖然架構已經切換,但是異步渲染的能力并沒有正式上線,我們只能在實驗版中使用。異步渲染指的是 Concurrent 模式,下面是官網的介紹:

“Concurrent 模式是 React 的新功能,可幫助應用保持響應,并根據用戶的設備性能和網速進行適當的調整。

優點

除了 Concurrent 模式,React 還提供了另外兩個模式, Legacy 模式依舊是同步更新的方式,可以認為和舊版本保持一致的兼容模式,而 Blocking 模式是一個過渡版本。

模式差異

 

Concurrent 模式說白就是讓組件更新異步化,切分時間片,渲染之前的調度、diff、更新都只在指定時間片進行,如果超時就暫停放到下個時間片進行,中途給瀏覽器一個喘息的時間。

“瀏覽器是單線程,它將 GUI 描繪,時間器處理,事件處理,JS 執行,遠程資源加載統統放在一起。當做某件事,只有將它做完才能做下一件事。如果有足夠的時間,瀏覽器是會對我們的代碼進行編譯優化(JIT)及進行熱代碼優化,一些 DOM 操作,內部也會對 reflow 進行處理。reflow 是一個性能黑洞,很可能讓頁面的大多數元素進行重新布局。瀏覽器的運作流程: 渲染 -> tasks -> 渲染 -> tasks -> 渲染 -> ....這些 tasks 中有些我們可控,有些不可控,比如 setTimeout 什么時候執行不好說,它總是不準時;資源加載時間不可控。但一些JS我們可以控制,讓它們分派執行,tasks的時長不宜過長,這樣瀏覽器就有時間優化 JS 代碼與修正 reflow !總結一句,就是讓瀏覽器休息好,瀏覽器就能跑得更快。-- by 司徒正美 《React Fiber架構》

模式差異

這里有個 demo,上面是一個🌟圍繞☀️運轉的動畫,下面是 React 定時 setState 更新視圖,同步模式下,每次 setState 都會造成上面的動畫卡頓,而異步模式下的動畫就很流暢。

同步模式:

同步模式

 

異步模式:

異步模式

 

如何使用

雖然很多文章都在介紹 Concurrent 模式,但是這個能力并沒有真正上線,想要使用只能安裝實驗版本。也可以直接通過這個 cdn :https://unpkg.com/browse/react@0.0.0-experimental-94c0244ba/ 。

  1. npm install react@experimental react-dom@experimental 

如果要開啟 Concurrent 模式,不能使用之前的 ReactDOM.render,需要替換成 ReactDOM.createRoot,而在實驗版本中,由于 API 不夠穩定, 需要通過 ReactDOM.unstable_createRoot來啟用 Concurrent 模式。

  1. import ReactDOM from 'react-dom'
  2. import App from './App'
  3.  
  4. ReactDOM.unstable_createRoot( 
  5.   document.getElementById('root'
  6. ).render(<App />); 

setState 合并更新

還記得之前 React15 的案例中,setTimeout 中進行 setState ,state.val 的值會立即發生變化。同樣的代碼,我們拿到 Concurrent 模式下運行一次。

  1. import React from 'react'
  2.  
  3. class App extends React.Component { 
  4.   state = { val: 0 } 
  5.   componentDidMount() { 
  6.     setTimeout(() => { 
  7.       // 第一次調用 
  8.       this.setState({ val: this.state.val + 1 }); 
  9.       console.log('first setState', this.state); 
  10.    
  11.       // 第二次調用 
  12.       this.setState({ val: this.state.val + 1 }); 
  13.       console.log('second setState', this.state); 
  14.        
  15.       this.setState({ val: this.state.val + 1 }, () => { 
  16.         console.log(this.state); 
  17.       }); 
  18.     }); 
  19.   } 
  20.   render() { 
  21.     return <div> val: { this.state.val } </div> 
  22.   } 
  23.  
  24. export default App; 

控制臺輸出

 

說明在 Concurrent 模式下,即使脫離了 React 的生命周期,setState 依舊能夠合并更新。主要原因是 Concurrent 模式下,真正的更新操作被移到了下一個事件隊列中,類似于 Vue 的 nextTick。

更新機制變更

我們修改一下 demo,然后看下點擊按鈕之后的調用棧。

  1. import React from 'react'
  2.  
  3. class App extends React.Component { 
  4.   state = { val: 0 } 
  5.   clickBtn() { 
  6.     this.setState({ val: this.state.val + 1 }); 
  7.   } 
  8.   render() { 
  9.     return (<div> 
  10.       <button onClick={() => {this.clickBtn()}}>click add</button> 
  11.       <div>val: { this.state.val }</div> 
  12.     </div>) 
  13.   } 
  14.  
  15. export default App; 

調用棧

調用棧

 

onClick 觸發后,進行 setState 操作,然后調用 enquueState 方法,到這里看起來好像和之前的模式一樣,但是后面的操作基本都變了,因為 React 16 中已經沒有了事務一說。

  1. Component.setState() => enquueState() => scheduleUpdate() => scheduleCallback() 
  2. => requestHostCallback(flushWork) => postMessage() 

真正的異步化邏輯就在 requestHostCallback、postMessage 里面,這是 React 內部自己實現的一個調度器:https://github.com/facebook/react/blob/v16.13.1/packages/scheduler/index.js。

  1. function unstable_scheduleCallback(priorityLevel, calback) { 
  2.   var currentTime = getCurrentTime(); 
  3.   var startTime = currentTime + delay; 
  4.   var newTask = { 
  5.     id: taskIdCounter++, 
  6.     startTime: startTime,           // 任務開始時間 
  7.     expirationTime: expirationTime, // 任務終止時間 
  8.     priorityLevel: priorityLevel,   // 調度優先級 
  9.     callback: callback,             // 回調函數 
  10.   }; 
  11.   if (startTime > currentTime) { 
  12.     // 超時處理,將任務放到 taskQueue,下一個時間片執行 
  13.     // 源碼中其實是 timerQueue,后續會有個操作將 timerQueue 的 task 轉移到 taskQueue 
  14.    push(taskQueue, newTask) 
  15.   } else { 
  16.     requestHostCallback(flushWork); 
  17.   } 
  18.   return newTask; 

requestHostCallback 的實現依賴于 MessageChannel,但是 MessageChannel 在這里并不是做消息通信用的,而是利用它的異步能力,給瀏覽器一個喘息的機會。說起 MessageChannel,Vue 2.5 的 nextTick 也有使用,但是 2.6 發布時又取消了。

vue@2.5

 

MessageChannel 會暴露兩個對象,port1 和 port2,port1 發送的消息能被 port2 接收,同樣 port2 發送的消息也能被 port1 接收,只是接收消息的時機會放到下一個 macroTask 中。

  1. var { port1, port2 } = new MessageChannel(); 
  2. // port1 接收 port2 的消息 
  3. port1.onmessage = function (msg) { console.log('MessageChannel exec') } 
  4. // port2 發送消息 
  5. port2.postMessage(null
  6.  
  7. new Promise(r => r()).then(() => console.log('promise exec')) 
  8. setTimeout(() => console.log('setTimeout exec')) 
  9.  
  10. console.log('start run'

執行結果

 

可以看到,port1 接收消息的時機比 Promise 所在的 microTask 要晚,但是早于 setTimeout。React 利用這個能力,給了瀏覽器一個喘息的時間,不至于被餓死。

還是之前的案例,同步更新時沒有給瀏覽器任何喘息,造成視圖的卡頓。

同步更新

 

異步更新時,拆分了時間片,給了瀏覽器充分的時間更新動畫。

異步更新

 

還是回到代碼層面,看看 React 是如何利用 MessageChannel 的。

  1. var isMessageLoopRunning = false; // 更新狀態 
  2. var scheduledHostCallback = null; // 全局的回調 
  3. var channel = new MessageChannel(); 
  4. var port = channel.port2; 
  5.  
  6. channel.port1.onmessage = function () { 
  7.   if (scheduledHostCallback !== null) { 
  8.     var currentTime = getCurrentTime(); 
  9.     // 重置超時時間 
  10.     deadline = currentTime + yieldInterval; 
  11.     var hasTimeRemaining = true
  12.  
  13.     // 執行 callback 
  14.     var hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime); 
  15.  
  16.     if (!hasMoreWork) { 
  17.       // 已經沒有任務了,修改狀態 
  18.       isMessageLoopRunning = false
  19.       scheduledHostCallback = null
  20.     } else { 
  21.       // 還有任務,放到下個任務隊列執行,給瀏覽器喘息的機會 
  22.       port.postMessage(null); 
  23.     } 
  24.   } else { 
  25.     isMessageLoopRunning = false
  26.   } 
  27. }; 
  28.  
  29. requestHostCallback = function (callback) { 
  30.   // callback 掛載到 scheduledHostCallback 
  31.   scheduledHostCallback = callback; 
  32.  
  33.   if (!isMessageLoopRunning) { 
  34.     isMessageLoopRunning = true
  35.     // 推送消息,下個隊列隊列調用 callback 
  36.     port.postMessage(null); 
  37.   } 
  38. }; 

再看看之前傳入的 callback(flushWork),調用 workLoop,取出 taskQueue 中的任務執行。

  1. // 精簡了相當多的代碼 
  2. function flushWork(hasTimeRemaining, initialTime) { 
  3.   return workLoop(hasTimeRemaining, initialTime); 
  4.  
  5. function workLoop(hasTimeRemaining, initialTime) { 
  6.   var currentTime = initialTime; 
  7.   // scheduleCallback 進行了 taskQueue 的 push 操作 
  8.   // 這里是獲取之前時間片未執行的操作 
  9.   currentTask = peek(taskQueue); 
  10.  
  11.   while (currentTask !== null) { 
  12.     if (currentTask.expirationTime > currentTime) { 
  13.       // 超時需要中斷任務 
  14.       break; 
  15.     } 
  16.  
  17.     currentTask.callback();         // 執行任務回調 
  18.     currentTime = getCurrentTime(); // 重置當前時間 
  19.     currentTask = peek(taskQueue);  // 獲取新的任務 
  20.   } 
  21.  // 如果當前任務不為空,表明是超時中斷,返回 true 
  22.   if (currentTask !== null) { 
  23.     return true
  24.   } else { 
  25.     return false
  26.   } 

可以看出,React 通過 expirationTime 來判斷是否超時,如果超時就把任務放到后面來執行。所以,異步模型中 setTimeout 里面進行 setState,只要當前時間片沒有結束(currentTime 小于 expirationTime),依舊可以將多個 setState 合并成一個。

接下來我們再做一個實驗,在 setTimeout 中連續進行 500 次的 setState,看看最后生效的次數。

  1. import React from 'react'
  2.  
  3. class App extends React.Component { 
  4.   state = { val: 0 } 
  5.   clickBtn() { 
  6.     for (let i = 0; i < 500; i++) { 
  7.       setTimeout(() => { 
  8.         this.setState({ val: this.state.val + 1 }); 
  9.       }) 
  10.     } 
  11.   } 
  12.   render() { 
  13.     return (<div> 
  14.       <button onClick={() => {this.clickBtn()}}>click add</button> 
  15.       <div>val: { this.state.val }</div> 
  16.     </div>) 
  17.   } 
  18.  
  19. export default App; 

 

先看看同步模式下:

同步模式

 

再看看異步模式下:


 

異步模式

 

最后 setState 的次數是 81 次,表明這里的操作在 7 個時間片下進行的。

總結這篇文章前后花費時間比較久,看 React 的源碼確實很痛苦,因為之前沒有了解過,剛開始是看一些文章的分析,但是很多模棱兩可的地方,無奈只能在源碼上進行 debug,而且一次性看了 React 15、16 兩個版本的代碼,感覺腦子都有些不夠用了。

 

當然這篇文章只是簡單介紹了更新機制從同步到異步的過程,其實 React 16 的更新除了異步之外,在時間片的劃分、任務的優先級上還有很多細節,這些東西放到下篇文章來講,不知不覺又是一個新坑。

本文轉載自微信公眾號「更了不起的前端」,可以通過以下二維碼關注。轉載本文請聯系更了不起的前端公眾號。

 

責任編輯:武曉燕 來源: 更了不起的前端
相關推薦

2020-09-30 09:15:24

React架構遞歸

2024-08-14 08:16:53

2020-10-28 09:12:48

React架構Hooks

2022-06-13 06:20:42

setStatereact18

2020-10-13 08:36:30

React 架構機制

2019-07-04 15:16:42

數據架構Flink數據倉庫

2018-06-05 08:36:47

內部部署云存儲

2023-08-09 08:00:00

數據倉庫數據架構

2024-12-30 09:55:44

2013-05-29 10:33:16

2015-06-15 09:29:56

聯想互聯網

2020-09-24 22:54:46

大數據IT技術

2017-06-29 09:28:37

OracleMariaDB復制

2023-05-29 13:56:00

JSReact

2009-07-01 09:46:14

火狐界面瀏覽器

2018-03-28 17:18:26

大數據

2024-04-26 09:13:34

RPCHTTP協議

2023-08-18 09:00:00

Kubernetes數據庫SQL

2020-10-27 07:29:43

架構系統流量

2020-12-09 08:12:30

系統架構
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产欧美一区二区久久性色99 | 精品一二三区 | 欧美一区二区三区免费电影 | 久久久精品一区二区三区 | 免费av在线网站 | 亚洲 欧美 日韩在线 | 免费在线观看黄网站 | 婷婷开心激情综合五月天 | 国产视频精品在线观看 | 久久国产精品久久久久久 | av一区二区三区四区 | 欧美一区视频 | 国产成人99久久亚洲综合精品 | 国产日韩欧美一区二区 | 国产精品成人久久久久a级 久久蜜桃av一区二区天堂 | 国产综合在线视频 | 亚洲入口 | 色综合久久久 | 天天干国产 | 久久精品亚洲欧美日韩精品中文字幕 | 日本成人三级电影 | 精品国产一区二区三区在线观看 | 黄色片在线看 | 亚洲国产成人精品女人久久久 | 午夜影院在线观看视频 | 国产精品99精品久久免费 | 中文欧美日韩 | 日日日色 | 天天看天天干 | 精品久久99| 国产成人午夜电影网 | 国产精品久久久久久久7电影 | 国产精品一级在线观看 | 九九热这里 | 在线视频一区二区 | 日韩在线不卡视频 | 国产精品一区在线观看你懂的 | 一区二区三区免费看 | 国产色在线 | 国产日韩欧美一区二区 | 成人久久久 |