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

React 架構(gòu)的演變 - 從遞歸到循環(huán)

開發(fā) 架構(gòu)
React 15 的遞歸更新邏輯是先將需要更新的組件放入臟組件隊列(這里在上篇文章已經(jīng)介紹過,沒看過的可以先看看《React 架構(gòu)的演變 - 從同步到異步》),然后取出組件進(jìn)行一次遞歸,不停向下尋找子節(jié)點來查找是否需要更新。

[[344647]]

遞歸更新的實現(xiàn)

React 15 的遞歸更新邏輯是先將需要更新的組件放入臟組件隊列(這里在上篇文章已經(jīng)介紹過,沒看過的可以先看看《React 架構(gòu)的演變 - 從同步到異步》),然后取出組件進(jìn)行一次遞歸,不停向下尋找子節(jié)點來查找是否需要更新。

下面使用一段代碼來簡單描述一下這個過程:

  1. updateComponent (prevElement, nextElement) { 
  2.   if ( 
  3.   // 如果組件的 type 和 key 都沒有發(fā)生變化,進(jìn)行更新 
  4.     prevElement.type === nextElement.type && 
  5.     prevElement.key === nextElement.key 
  6.   ) { 
  7.     // 文本節(jié)點更新 
  8.     if (prevElement.type === 'text') { 
  9.         if (prevElement.value !== nextElement.value) { 
  10.             this.replaceText(nextElement.value) 
  11.         } 
  12.     } 
  13.     // DOM 節(jié)點的更新 
  14.     else { 
  15.       // 先更新 DOM 屬性 
  16.       this.updateProps(prevElement, nextElement) 
  17.       // 再更新 children 
  18.       this.updateChildren(prevElement, nextElement) 
  19.     } 
  20.   } 
  21.   // 如果組件的 type 和 key 發(fā)生變化,直接重新渲染組件 
  22.   else { 
  23.     // 觸發(fā) unmount 生命周期 
  24.     ReactReconciler.unmountComponent(prevElement) 
  25.     // 渲染新的組件 
  26.     this._instantiateReactComponent(nextElement) 
  27.   } 
  28. }, 
  29. updateChildren (prevElement, nextElement) { 
  30.   var prevChildren = prevElement.children 
  31.   var nextChildren = nextElement.children 
  32.   // 省略通過 key 重新排序的 diff 過程 
  33.   if (prevChildren === null) { } // 渲染新的子節(jié)點 
  34.   if (nextChildren === null) { } // 清空所有子節(jié)點 
  35.   // 子節(jié)點對比 
  36.   prevChildren.forEach((prevChild, index) => { 
  37.     const nextChild = nextChildren[index
  38.     // 遞歸過程 
  39.     this.updateComponent(prevChild, nextChild) 
  40.   }) 

為了更清晰的看到這個過程,我們還是寫一個簡單的Demo,構(gòu)造一個 3 * 3 的 Table 組件。

Table

  1. // https://codesandbox.io/embed/react-sync-demo-nlijf 
  2. class Col extends React.Component { 
  3.   render() { 
  4.     // 渲染之前暫停 8ms,給 render 制造一點點壓力 
  5.     const start = performance.now() 
  6.     while (performance.now() - start < 8) 
  7.     return <td>{this.props.children}</td> 
  8.   } 
  9.  
  10. export default class Demo extends React.Component { 
  11.   state = { 
  12.     val: 0 
  13.   } 
  14.   render() { 
  15.     const { val } = this.state 
  16.     const array = Array(3).fill() 
  17.     // 構(gòu)造一個 3 * 3 表格 
  18.     const rows = array.map( 
  19.       (_, row) => <tr key={row}> 
  20.         {array.map( 
  21.           (_, col) => <Col key={col}>{val}</Col> 
  22.         )} 
  23.       </tr> 
  24.     ) 
  25.     return ( 
  26.       <table className="table"
  27.         <tbody>{rows}</tbody> 
  28.       </table
  29.     ) 
  30.   } 

然后每秒對 Table 里面的值更新一次,讓 val 每次 + 1,從 0 ~ 9 不停循環(huán)。

Table Loop

  1. // https://codesandbox.io/embed/react-sync-demo-nlijf 
  2. export default class Demo extends React.Component { 
  3.  tick = () => { 
  4.     setTimeout(() => { 
  5.       this.setState({ val: next < 10 ? next : 0 }) 
  6.       this.tick() 
  7.     }, 1000) 
  8.   } 
  9.   componentDidMount() { 
  10.     this.tick() 
  11.   } 

完整代碼的線上地址:https://codesandbox.io/embed/react-sync-demo-nlijf。Demo 組件每次調(diào)用 setState,React 會先判斷該組件的類型有沒有發(fā)生修改,如果有就整個組件進(jìn)行重新渲染,如果沒有會更新 state,然后向下判斷 table 組件,table 組件繼續(xù)向下判斷 tr 組件,tr 組件再向下判斷 td 組件,最后發(fā)現(xiàn) td 組件下的文本節(jié)點發(fā)生了修改,通過 DOM API 更新。

Update

 

通過 Performance 的函數(shù)調(diào)用堆棧也能清晰的看到這個過程,updateComponent 之后 的 updateChildren 會繼續(xù)調(diào)用子組件的 updateComponent,直到遞歸完所有組件,表示更新完成。

調(diào)用堆棧

 

遞歸的缺點很明顯,不能暫停更新,一旦開始必須從頭到尾,這與 React 16 拆分時間片,給瀏覽器喘口氣的理念明顯不符,所以 React 必須要切換架構(gòu),將虛擬 DOM 從樹形結(jié)構(gòu)修改為鏈表結(jié)構(gòu)。

可循環(huán)的 Fiber

這里說的鏈表結(jié)構(gòu)就是 Fiber 了,鏈表結(jié)構(gòu)最大的優(yōu)勢就是可以通過循環(huán)的方式來遍歷,只要記住當(dāng)前遍歷的位置,即使中斷后也能快速還原,重新開始遍歷。

我們先看看一個 Fiber 節(jié)點的數(shù)據(jù)結(jié)構(gòu):

  1. function FiberNode (tag, key) { 
  2.   // 節(jié)點 key,主要用于了優(yōu)化列表 diff 
  3.   this.key = key 
  4.   // 節(jié)點類型;FunctionComponent: 0, ClassComponent: 1, HostRoot: 3 ... 
  5.   this.tag = tag 
  6.  
  7.  // 子節(jié)點 
  8.   this.child = null 
  9.   // 父節(jié)點 
  10.   this.return = null  
  11.   // 兄弟節(jié)點 
  12.   this.sibling = null 
  13.    
  14.   // 更新隊列,用于暫存 setState 的值 
  15.   this.updateQueue = null 
  16.    
  17.   // 節(jié)點更新過期時間,用于時間分片 
  18.   // react 17 改為:lanes、childLanes 
  19.   this.expirationTime = NoLanes 
  20.   this.childExpirationTime = NoLanes 
  21.  
  22.   // 對應(yīng)到頁面的真實 DOM 節(jié)點 
  23.   this.stateNode = null 
  24.   // Fiber 節(jié)點的副本,可以理解為備胎,主要用于提升更新的性能 
  25.   this.alternate = null 

下面舉個例子,我們這里有一段普通的 HTML 文本:

  1. <table class="table"
  2.   <tr> 
  3.     <td>1</td> 
  4.     <td>1</td> 
  5.   </tr> 
  6.   <tr> 
  7.     <td>1</td> 
  8.   </tr> 
  9. </table

在之前的 React 版本中,jsx 會轉(zhuǎn)化為 createElement 方法,創(chuàng)建樹形結(jié)構(gòu)的虛擬 DOM。

  1. const VDOMRoot = { 
  2.   type: 'table'
  3.   props: { className: 'table' }, 
  4.   children: [ 
  5.     { 
  6.       type: 'tr'
  7.       props: { }, 
  8.       children: [ 
  9.         { 
  10.           type: 'td'
  11.           props: { }, 
  12.           children: [{type: 'text', value: '1'}] 
  13.         }, 
  14.         { 
  15.           type: 'td'
  16.           props: { }, 
  17.           children: [{type: 'text', value: '1'}] 
  18.         } 
  19.       ] 
  20.     }, 
  21.     { 
  22.       type: 'tr'
  23.       props: { }, 
  24.       children: [ 
  25.         { 
  26.           type: 'td'
  27.           props: { }, 
  28.           children: [{type: 'text', value: '1'}] 
  29.         } 
  30.       ] 
  31.     } 
  32.   ] 

Fiber 架構(gòu)下,結(jié)構(gòu)如下:

  1. // 有所簡化,并非與 React 真實的 Fiber 結(jié)構(gòu)一致 
  2. const FiberRoot = { 
  3.   type: 'table'
  4.   returnnull
  5.   sibling: null
  6.   child: { 
  7.     type: 'tr'
  8.     return: FiberNode, // table 的 FiberNode 
  9.     sibling: { 
  10.       type: 'tr'
  11.       return: FiberNode, // table 的 FiberNode 
  12.       sibling: null
  13.       child: { 
  14.         type: 'td'
  15.         return: FiberNode, // tr 的 FiberNode 
  16.         sibling: { 
  17.           type: 'td'
  18.           return: FiberNode, // tr 的 FiberNode 
  19.           sibling: null
  20.           child: null
  21.           text: '1' // 子節(jié)點僅有文本節(jié)點 
  22.         }, 
  23.         child: null
  24.         text: '1' // 子節(jié)點僅有文本節(jié)點 
  25.       } 
  26.     }, 
  27.     child: { 
  28.       type: 'td'
  29.       return: FiberNode, // tr 的 FiberNode 
  30.       sibling: null
  31.       child: null
  32.       text: '1' // 子節(jié)點僅有文本節(jié)點 
  33.     } 
  34.   } 

Fiber

 

循環(huán)更新的實現(xiàn)

那么,在 setState 的時候,React 是如何進(jìn)行一次 Fiber 的遍歷的呢?

  1. let workInProgress = FiberRoot 
  2.  
  3. // 遍歷 Fiber 節(jié)點,如果時間片時間用完就停止遍歷 
  4. function workLoopConcurrent() { 
  5.   while ( 
  6.     workInProgress !== null && 
  7.     !shouldYield() // 用于判斷當(dāng)前時間片是否到期 
  8.   ) { 
  9.     performUnitOfWork(workInProgress) 
  10.   } 
  11.  
  12. function performUnitOfWork() { 
  13.   const next = beginWork(workInProgress) // 返回當(dāng)前 Fiber 的 child 
  14.   if (next) { // child 存在 
  15.     // 重置 workInProgress 為 child 
  16.     workInProgress = next 
  17.   } else { // child 不存在 
  18.     // 向上回溯節(jié)點 
  19.     let completedWork = workInProgress 
  20.     while (completedWork !== null) { 
  21.       // 收集副作用,主要是用于標(biāo)記節(jié)點是否需要操作 DOM 
  22.       completeWork(completedWork) 
  23.  
  24.       // 獲取 Fiber.sibling 
  25.       let siblingFiber = workInProgress.sibling 
  26.       if (siblingFiber) { 
  27.         // sibling 存在,則跳出 complete 流程,繼續(xù) beginWork 
  28.         workInProgress = siblingFiber 
  29.         return
  30.       } 
  31.  
  32.       completedWork = completedWork.return 
  33.       workInProgress = completedWork 
  34.     } 
  35.   } 
  36.  
  37. function beginWork(workInProgress) { 
  38.   // 調(diào)用 render 方法,創(chuàng)建子 Fiber,進(jìn)行 diff 
  39.   // 操作完畢后,返回當(dāng)前 Fiber 的 child 
  40.   return workInProgress.child 
  41. function completeWork(workInProgress) { 
  42.   // 收集節(jié)點副作用 

Fiber 的遍歷本質(zhì)上就是一個循環(huán),全局有一個 workInProgress 變量,用來存儲當(dāng)前正在 diff 的節(jié)點,先通過 beginWork 方法對當(dāng)前節(jié)點然后進(jìn)行 diff 操作(diff 之前會調(diào)用 render,重新計算 state、prop),并返回當(dāng)前節(jié)點的第一個子節(jié)點( fiber.child)作為新的工作節(jié)點,直到不存在子節(jié)點。然后,對當(dāng)前節(jié)點調(diào)用 completedWork 方法,存儲 beginWork 過程中產(chǎn)生的副作用,如果當(dāng)前節(jié)點存在兄弟節(jié)點( fiber.sibling),則將工作節(jié)點修改為兄弟節(jié)點,重新進(jìn)入 beginWork 流程。直到 completedWork 重新返回到根節(jié)點,執(zhí)行 commitRoot將所有的副作用反應(yīng)到真實 DOM 中。

Fiber work loop

 

在一次遍歷過程中,每個節(jié)點都會經(jīng)歷 beginWork、completeWork ,直到返回到根節(jié)點,最后通過 commitRoot 將所有的更新提交,關(guān)于這部分的內(nèi)容可以看:《React 技術(shù)揭秘》。

時間分片的秘密

前面說過,F(xiàn)iber 結(jié)構(gòu)的遍歷是支持中斷恢復(fù),為了觀察這個過程,我們將之前的 3 * 3 的 Table 組件改成 Concurrent 模式,線上地址:https://codesandbox.io/embed/react-async-demo-h1lbz。由于每次調(diào)用 Col 組件的 render 部分需要耗時 8ms,會超出了一個時間片,所以每個 td 部分都會暫停一次。

  1. class Col extends React.Component { 
  2.   render() { 
  3.     // 渲染之前暫停 8ms,給 render 制造一點點壓力 
  4.     const start = performance.now(); 
  5.     while (performance.now() - start < 8); 
  6.     return <td>{this.props.children}</td> 
  7.   } 

在這個 3 * 3 組件里,一共有 9 個 Col 組件,所以會有 9 次耗時任務(wù),分散在 9 個時間片進(jìn)行,通過 Performance 的調(diào)用??梢钥吹骄唧w情況:

異步模式的調(diào)用棧

 

在非 Concurrent 模式下,F(xiàn)iber 節(jié)點的遍歷是一次性進(jìn)行的,并不會切分多個時間片,差別就是在遍歷的時候調(diào)用了 workLoopSync 方法,該方法并不會判斷時間片是否用完。

  1. // 遍歷 Fiber 節(jié)點 
  2. function workLoopSync() { 
  3.   while (workInProgress !== null) { 
  4.     performUnitOfWork(workInProgress) 
  5.   } 

同步模式的調(diào)用棧

 

通過上面的分析可以看出, shouldYield 方法決定了當(dāng)前時間片是否已經(jīng)用完,這也是決定 React 是同步渲染還是異步渲染的關(guān)鍵。如果去除任務(wù)優(yōu)先級的概念,shouldYield 方法可以說很簡單,就是判斷了當(dāng)前的時間,是否已經(jīng)超過了預(yù)設(shè)的 deadline。

  1. function getCurrentTime() { 
  2.   return performance.now() 
  3. function shouldYield() { 
  4.   // 獲取當(dāng)前時間 
  5.   var currentTime = getCurrentTime() 
  6.   return currentTime >= deadline 

deadline 又是如何得的呢?可以回顧上一篇文章(《React 架構(gòu)的演變 - 從同步到異步》)提到的 ChannelMessage,更新開始的時候會通過 requestHostCallback(即:port2.send)發(fā)送異步消息,在 performWorkUntilDeadline (即:port1.onmessage)中接收消息。performWorkUntilDeadline 每次接收到消息時,表示已經(jīng)進(jìn)入了下一個任務(wù)隊列,這個時候就會更新 deadline。

異步調(diào)用棧

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

超時時間的設(shè)置就是在當(dāng)前時間的基礎(chǔ)上加上了一個 yieldInterval, 這個 yieldInterval的值,默認(rèn)是 5ms。

  1. deadline = currentTime + yieldInterval 

同時 React 也提供了修改 yieldInterval 的手段,通過手動指定 fps,來確定一幀的具體時間(單位:ms),fps 越高,一個時間分片的時間就越短,對設(shè)備的性能要求就越高。

  1. forceFrameRate = function (fps) { 
  2.   if (fps < 0 || fps > 125) { 
  3.     // 幀率僅支持 0~125 
  4.     return 
  5.   } 
  6.  
  7.   if (fps > 0) { 
  8.     // 一般 60 fps 的設(shè)備 
  9.     // 一個時間分片的時間為 Math.floor(1000/60) = 16 
  10.     yieldInterval = Math.floor(1000 / fps) 
  11.   } else { 
  12.     // reset the framerate 
  13.     yieldInterval = 5 
  14.   } 

總結(jié)

下面我們將異步邏輯、循環(huán)更新、時間分片串聯(lián)起來。先回顧一下之前的文章講過,Concurrent 模式下,setState 后的調(diào)用順序:

  1. Component.setState() 
  2.   => enqueueSetState() 
  3.  => scheduleUpdate() 
  4.   => scheduleCallback(performConcurrentWorkOnRoot) 
  5.   => requestHostCallback() 
  6.   => postMessage() 
  7.   => performWorkUntilDeadline() 

scheduleCallback 方法會將傳入的回調(diào)(performConcurrentWorkOnRoot)組裝成一個任務(wù)放入 taskQueue 中,然后調(diào)用 requestHostCallback 發(fā)送一個消息,進(jìn)入異步任務(wù)。performWorkUntilDeadline 接收到異步消息,從 taskQueue 取出任務(wù)開始執(zhí)行,這里的任務(wù)就是之前傳入的 performConcurrentWorkOnRoot 方法,這個方法最后會調(diào)用workLoopConcurrent(workLoopConcurrent 前面已經(jīng)介紹過了,這個不再重復(fù))。如果 workLoopConcurrent 是由于超時中斷的,hasMoreWork 返回為 true,通過 postMessage 發(fā)送消息,將操作延遲到下一個任務(wù)隊列。

 


 

流程圖

 

 

到這里整個流程已經(jīng)結(jié)束,希望大家看完文章能有所收獲,下一篇文章會介紹 Fiber 架構(gòu)下 Hook 的實現(xiàn)。

本文轉(zhuǎn)載自微信公眾號「更了不起的前端」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系更了不起的前端公眾號。

 

責(zé)任編輯:武曉燕 來源: 更了不起的前端
相關(guān)推薦

2020-09-24 08:45:10

React架構(gòu)源碼

2020-10-28 09:12:48

React架構(gòu)Hooks

2020-10-13 08:36:30

React 架構(gòu)機(jī)制

2024-08-14 08:16:53

2023-05-29 13:56:00

JSReact

2019-07-04 15:16:42

數(shù)據(jù)架構(gòu)Flink數(shù)據(jù)倉庫

2019-04-18 14:24:52

技術(shù)互聯(lián)網(wǎng)架構(gòu)

2022-11-15 17:31:35

邊緣計算架構(gòu)人工智能

2017-08-02 16:44:32

架構(gòu)

2023-08-09 08:00:00

數(shù)據(jù)倉庫數(shù)據(jù)架構(gòu)

2018-06-05 08:36:47

內(nèi)部部署云存儲

2024-05-10 09:36:36

架構(gòu)消息隊列

2009-08-26 18:20:42

三層架構(gòu)

2021-04-20 14:57:20

架構(gòu)運(yùn)維技術(shù)

2022-07-04 08:14:24

架構(gòu)演變Tomcat容器架構(gòu)

2013-05-29 10:33:16

2024-12-30 09:55:44

2021-05-12 23:07:16

服務(wù)器處理連接

2014-06-17 14:01:34

Mysql網(wǎng)站架構(gòu)

2022-08-15 09:00:00

JavaScript前端架構(gòu)
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 欧美日韩最新 | 日韩激情在线 | 精品国产乱码久久久久久闺蜜 | 国产精品一区二区三区在线 | 国产一区二区激情视频 | 国产精品成人一区二区三区 | www97影院 | 精品免费视频 | 天天狠狠 | 免费精品 | 国产高清在线精品一区二区三区 | 91在线一区 | 国产一区91精品张津瑜 | 国产目拍亚洲精品99久久精品 | 一色一黄视频 | 亚洲成人一二区 | 欧美精品一区在线发布 | 中文字幕第三页 | 武道仙尊动漫在线观看 | 在线一区二区三区 | 99精品视频免费观看 | 国产精品一区二区在线 | 国产精品99久久久久久人 | av在线免费观看不卡 | 国产精品一二三区 | 青青草久久 | 中国一级大黄大片 | 中文字幕日韩专区 | 亚洲精品国产a久久久久久 中文字幕一区二区三区四区五区 | 91成人免费观看 | 天天弄天天操 | 91精品国产综合久久福利软件 | 欧美一级黄色网 | 免费看一区二区三区 | 亚洲女人的天堂 | 久久久91精品国产一区二区三区 | 日韩久久久久 | 91精品欧美久久久久久久 | 精品久久久久国产免费第一页 | 精品乱码一区二区 | 日韩在线第一 |