日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
React 架構(gòu)的演變 - 更新機制

前面的文章分析了 Concurrent 模式下異步更新的邏輯,以及 Fiber 架構(gòu)是如何進(jìn)行時間分片的,更新過程中的很多內(nèi)容都省略了,評論區(qū)也收到了一些同學(xué)對更新過程的疑惑,今天的文章就來講解下 React Fiber 架構(gòu)的更新機制。

為定襄等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計制作服務(wù),及定襄網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為網(wǎng)站設(shè)計制作、成都做網(wǎng)站、定襄網(wǎng)站設(shè)計,以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!

Fiber 數(shù)據(jù)結(jié)構(gòu)

我們先回顧一下 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.   // 新傳入的 props 
  17.   this.pendingProps = pendingProps; 
  18.   // 之前的 props 
  19.   this.memoizedProps = null; 
  20.   // 之前的 state 
  21.   this.memoizedState = null; 
  22.  
  23.   // 節(jié)點更新過期時間,用于時間分片 
  24.   // react 17 改為:lanes、childLanes 
  25.   this.expirationTime = NoLanes 
  26.   this.childExpirationTime = NoLanes 
  27.  
  28.   // 對應(yīng)到頁面的真實 DOM 節(jié)點 
  29.   this.stateNode = null 
  30.   // Fiber 節(jié)點的副本,可以理解為備胎,主要用于提升更新的性能 
  31.   this.alternate = null 
  32.  
  33.   // 副作用相關(guān),用于標(biāo)記節(jié)點是否需要更新 
  34.   // 以及更新的類型:替換成新節(jié)點、更新屬性、更新文本、刪除…… 
  35.   this.effectTag = NoEffect 
  36.   // 指向下一個需要更新的節(jié)點 
  37.   this.nextEffect = null 
  38.   this.firstEffect = null 
  39.   this.lastEffect = null 

緩存機制

可以注意到 Fiber 節(jié)點有個 alternate 屬性,該屬性在節(jié)點初始化的時候默認(rèn)為空(this.alternate = null)。這個節(jié)點的作用就是用來緩存之前的 Fiber 節(jié)點,更新的時候會判斷 fiber.alternate 是否為空來確定當(dāng)前是首次渲染還是更新。下面我們上代碼:

 
 
 
 
  1. import React from 'react'; 
  2. import ReactDOM from 'react-dom'; 
  3.  
  4. class App extends React.Component { 
  5.   state = { val: 0 } 
  6.   render() { 
  7.     return 
    val: { this.state.val }
     
  8.   } 
  9.  
  10. ReactDOM.unstable_createRoot( 
  11.   document.getElementById('root') 
  12. ).render(

在調(diào)用 createRoot 的時候,會先生成一個FiberRootNode,在 FiberRootNode 下會有個 current 屬性,current 指向 RootFiber 可以理解為一個空 Fiber。后續(xù)調(diào)用的 render 方法,就是將傳入的組件掛載到 FiberRootNode.current(即 RootFiber) 的空 Fiber 節(jié)點上。

 
 
 
 
  1. // 實驗版本對外暴露的 createRoot 需要加上 `unstable_` 前綴 
  2. exports.unstable_createRoot = createRoot 
  3.  
  4. function createRoot(container) { 
  5.   return new ReactDOMRoot(container) 
  6. function ReactDOMRoot(container) { 
  7.   var root = new FiberRootNode() 
  8.   // createRootFiber => createFiber => return new FiberNode(tag); 
  9.   root.current = createRootFiber() // 掛載一個空的 fiber 節(jié)點 
  10.   this._internalRoot = root 
  11. ReactDOMRoot.prototype.render = function render(children) { 
  12.   var root = this._internalRoot 
  13.   var update = createUpdate() 
  14.   update.payload = { element: children } 
  15.   const rootFiber = root.current 
  16.   // update對象放到 rootFiber 的 updateQueue 中 
  17.   enqueueUpdate(rootFiber, update) 
  18.   // 開始更新流程 
  19.   scheduleUpdateOnFiber(rootFiber) 

render 最后調(diào)用 scheduleUpdateOnFiber 進(jìn)入更新任務(wù),該方法之前有說明,最后會通過 scheduleCallback 走 MessageChannel 消息進(jìn)入下個任務(wù)隊列,最后調(diào)用 performConcurrentWorkOnRoot 方法。

 
 
 
 
  1. // scheduleUpdateOnFiber 
  2. // => ensureRootIsScheduled 
  3. // => scheduleCallback(performConcurrentWorkOnRoot) 
  4. function performConcurrentWorkOnRoot(root) { 
  5.   renderRootConcurrent(root) 
  6. function renderRootConcurrent(root) { 
  7.   // workInProgressRoot 為空,則創(chuàng)建 workInProgress 
  8.   if (workInProgressRoot !== root) { 
  9.     createWorkInProgress() 
  10.   } 
  11. function createWorkInProgress() { 
  12.   workInProgressRoot = root 
  13.   var current = root.current 
  14.   var workInProgress = current.alternate; 
  15.   if (workInProgress === null) { 
  16.     // 第一次構(gòu)建,需要創(chuàng)建副本 
  17.     workInProgress = createFiber(current.tag) 
  18.     workInProgress.alternate = current 
  19.     current.alternate = workInProgress 
  20.   } else { 
  21.     // 更新過程可以復(fù)用 
  22.     workInProgress.nextEffect = null 
  23.     workInProgress.firstEffect = null 
  24.     workInProgress.lastEffect = null 
  25.   } 

開始更新時,如果 workInProgress 為空會指向一個新的空 Fiber 節(jié)點,表示正在進(jìn)行工作的 Fiber 節(jié)點。

 
 
 
 
  1. workInProgress.alternate = current 
  2. current.alternate = workInProgress 

fiber tree

構(gòu)造好 workInProgress 之后,就會開始在新的 RootFiber 下生成新的子 Fiber 節(jié)點了。

 
 
 
 
  1. function renderRootConcurrent(root) { 
  2.   // 構(gòu)造 workInProgress... 
  3.   // workInProgress.alternate = current 
  4.  // current.alternate = workInProgress 
  5.  
  6.   // 進(jìn)入遍歷 fiber 樹的流程 
  7.   workLoopConcurrent() 
  8.  
  9. function workLoopConcurrent() { 
  10.   while (workInProgress !== null && !shouldYield()) { 
  11.     performUnitOfWork() 
  12.   } 
  13.  
  14. function performUnitOfWork() { 
  15.   var current = workInProgress.alternate 
  16.   // 返回當(dāng)前 Fiber 的 child 
  17.   const next = beginWork(current, workInProgress) 
  18.   // 省略后續(xù)代碼... 

按照我們前面的案例, workLoopConcurrent 調(diào)用完成后,最后得到的 fiber 樹如下:

 
 
 
 
  1. class App extends React.Component { 
  2.   state = { val: 0 } 
  3.   render() { 
  4.     return 
    val: { this.state.val }
     
  5.   } 

fiber tree

最后進(jìn)入 Commit 階段的時候,會切換 FiberRootNode 的 current 屬性:

 
 
 
 
  1. function performConcurrentWorkOnRoot() { 
  2.   renderRootConcurrent() // 結(jié)束遍歷流程,fiber tree 已經(jīng)構(gòu)造完畢 
  3.  
  4.   var finishedWork = root.current.alternate 
  5.   root.finishedWork = finishedWork 
  6.   commitRoot(root) 
  7. function commitRoot() { 
  8.   var finishedWork = root.finishedWork 
  9.   root.finishedWork = null 
  10.   root.current = finishedWork // 切換到新的 fiber 樹 

fiber tree

上面的流程為第一次渲染,通過 setState({ val: 1 }) 更新時,workInProgress 會切換到 root.current.alternate。

 
 
 
 
  1. function createWorkInProgress() { 
  2.   workInProgressRoot = root 
  3.   var current = root.current 
  4.   var workInProgress = current.alternate; 
  5.   if (workInProgress === null) { 
  6.     // 第一次構(gòu)建,需要創(chuàng)建副本 
  7.     workInProgress = createFiber(current.tag) 
  8.     workInProgress.alternate = current 
  9.     current.alternate = workInProgress 
  10.   } else { 
  11.     // 更新過程可以復(fù)用 
  12.     workInProgress.nextEffect = null 
  13.     workInProgress.firstEffect = null 
  14.     workInProgress.lastEffect = null 
  15.   } 

fiber tree

在后續(xù)的遍歷過程中(workLoopConcurrent()),會在舊的 RootFiber 下構(gòu)建一個新的 fiber tree,并且每個 fiber 節(jié)點的 alternate 都會指向 current fiber tree 下的節(jié)點。

fiber tree

這樣 FiberRootNode 的 current 屬性就會輪流在兩棵 fiber tree 不停的切換,即達(dá)到了緩存的目的,也不會過分的占用內(nèi)存。

更新隊列

在 React 15 里,多次 setState 會被放到一個隊列中,等待一次更新。

 
 
 
 
  1. // setState 方法掛載到原型鏈上 
  2. ReactComponent.prototype.setState = function (partialState, callback) { 
  3.   // 調(diào)用 setState 后,會調(diào)用內(nèi)部的 updater.enqueueSetState 
  4.   this.updater.enqueueSetState(this, partialState) 
  5. }; 
  6.  
  7. var ReactUpdateQueue = { 
  8.   enqueueSetState(component, partialState) { 
  9.     // 在組件的 _pendingStateQueue 上暫存新的 state 
  10.     if (!component._pendingStateQueue) { 
  11.       component._pendingStateQueue = [] 
  12.     } 
  13.     // 將 setState 的值放入隊列中 
  14.     var queue = component._pendingStateQueue 
  15.     queue.push(partialState) 
  16.     enqueueUpdate(component) 
  17.   } 

同樣在 Fiber 架構(gòu)中,也會有一個隊列用來存放 setState 的值。每個 Fiber 節(jié)點都有一個 updateQueue 屬性,這個屬性就是用來緩存 setState 值的,只是結(jié)構(gòu)從 React 15 的數(shù)組變成了鏈表結(jié)構(gòu)。

無論是首次 Render 的 Mount 階段,還是 setState 的 Update 階段,內(nèi)部都會調(diào)用 enqueueUpdate 方法。

 
 
 
 
  1. // --- Render 階段 --- 
  2. function initializeUpdateQueue(fiber) { 
  3.   var queue = { 
  4.     baseState: fiber.memoizedState, 
  5.     firstBaseUpdate: null, 
  6.     lastBaseUpdate: null, 
  7.     shared: { 
  8.       pending: null 
  9.     }, 
  10.     effects: null 
  11.   } 
  12.   fiber.updateQueue = queue 
  13. ReactDOMRoot.prototype.render = function render(children) { 
  14.   var root = this._internalRoot 
  15.   var update = createUpdate() 
  16.   update.payload = { element: children } 
  17.   const rootFiber = root.current 
  18.   // 初始化 rootFiber 的 updateQueue 
  19.   initializeUpdateQueue(rootFiber) 
  20.   // update 對象放到 rootFiber 的 updateQueue 中 
  21.   enqueueUpdate(rootFiber, update) 
  22.   // 開始更新流程 
  23.   scheduleUpdateOnFiber(rootFiber) 
  24.  
  25. // --- Update 階段 --- 
  26. Component.prototype.setState = function (partialState, callback) { 
  27.   this.updater.enqueueSetState(this, partialState) 
  28. var classComponentUpdater = { 
  29.   enqueueSetState: function (inst, payload) { 
  30.     // 獲取實例對應(yīng)的fiber 
  31.     var fiber = get(inst) 
  32.     var update = createUpdate() 
  33.     update.payload = payload 
  34.  
  35.     // update 對象放到 rootFiber 的 updateQueue 中 
  36.     enqueueUpdate(fiber, update) 
  37.     scheduleUpdateOnFiber(fiber) 
  38.   } 

enqueueUpdate 方法的主要作用就是將 setState 的值掛載到 Fiber 節(jié)點上。

 
 
 
 
  1. function enqueueUpdate(fiber, update) { 
  2.   var updateQueue = fiber.updateQueue; 
  3.  
  4.   if (updateQueue === null) { 
  5.     // updateQueue 為空則跳過 
  6.     return; 
  7.   } 
  8.   var sharedQueue = updateQueue.shared; 
  9.   var pending = sharedQueue.pending; 
  10.  
  11.   if (pending === null) { 
  12.     update.next = update; 
  13.   } else { 
  14.     update.next = pending.next; 
  15.     pending.next = update; 
  16.   } 
  17.  
  18.   sharedQueue.pending = update; 

多次 setState 會在 sharedQueue.pending 上形成一個單向循環(huán)鏈表,具體例子更形象的展示下這個鏈表結(jié)構(gòu)。

 
 
 
 
  1. class App extends React.Component { 
  2.   state = { val: 0 } 
  3.   click () { 
  4.     for (let i = 0; i < 3; i++) { 
  5.       this.setState({ val: this.state.val + 1 }) 
  6.     } 
  7.   } 
  8.   render() { 
  9.     return  { 
  10.       this.click() 
  11.     }}>val: { this.state.val }
 
  •   } 
  • 點擊 div 之后,會連續(xù)進(jìn)行三次 setState,每次 setState 都會更新 updateQueue。

    第一次 setState

    第二次 setState

    第三次 setState

    更新過程中,我們遍歷下 updateQueue 鏈表,可以看到結(jié)果與預(yù)期的一致。

     
     
     
     
    1. let $pending = sharedQueue.pending 
    2. // 遍歷鏈表,在控制臺輸出 payload 
    3. while($pending) { 
    4.   console.log('update.payload', $pending.payload) 
    5.   $pending = $pending.next 

    鏈表數(shù)據(jù)

    遞歸 Fiber 節(jié)點

    Fiber 架構(gòu)下每個節(jié)點都會經(jīng)歷遞(beginWork)和歸(completeWork)兩個過程:

    • beginWork:生成新的 state,調(diào)用 render 創(chuàng)建子節(jié)點,連接當(dāng)前節(jié)點與子節(jié)點;
    • completeWork:依據(jù) EffectTag 收集 Effect,構(gòu)造 Effect List;

    先回顧下這個流程:

     
     
     
     
    1. function workLoopConcurrent() { 
    2.   while (workInProgress !== null && !shouldYield()) { 
    3.     performUnitOfWork() 
    4.   } 
    5.  
    6. function performUnitOfWork() { 
    7.   var current = workInProgress.alternate 
    8.   // 返回當(dāng)前 Fiber 的 child 
    9.   const next = beginWork(current, workInProgress) 
    10.   if (next === null) { // child 不存在 
    11.     completeUnitOfWork() 
    12.   } else { // child 存在 
    13.     // 重置 workInProgress 為 child 
    14.     workInProgress = next 
    15.   } 
    16. function completeUnitOfWork() { 
    17.   // 向上回溯節(jié)點 
    18.   let completedWork = workInProgress 
    19.   while (completedWork !== null) { 
    20.     // 收集副作用,主要是用于標(biāo)記節(jié)點是否需要操作 DOM 
    21.     var current = completedWork.alternate 
    22.     completeWork(current, completedWork) 
    23.  
    24.     // 省略構(gòu)造 Effect List 過程 
    25.  
    26.     // 獲取 Fiber.sibling 
    27.     let siblingFiber = workInProgress.sibling 
    28.     if (siblingFiber) { 
    29.       // sibling 存在,則跳出 complete 流程,繼續(xù) beginWork 
    30.       workInProgress = siblingFiber 
    31.       return 
    32.     } 
    33.  
    34.     completedWork = completedWork.return 
    35.     workInProgress = completedWork 
    36.   } 

    遞(beginWork)

    先看看 beginWork 進(jìn)行了哪些操作:

     
     
     
     
    1. function beginWork(current, workInProgress) { 
    2.   if (current !== null) { // current 不為空,表示需要進(jìn)行 update 
    3.     var oldProps = current.memoizedProps // 原先傳入的 props 
    4.     var newProps = workInProgress.pendingProps // 更新過程中新的 props 
    5.     // 組件的 props 發(fā)生變化,或者 type 發(fā)生變化 
    6.     if (oldProps !== newProps || workInProgress.type !== current.type) { 
    7.       // 設(shè)置更新標(biāo)志位為 true 
    8.       didReceiveUpdate = true 
    9.     } 
    10.   } else { // current 為空表示首次加載,需要進(jìn)行 mount 
    11.     didReceiveUpdate = false 
    12.   } 
    13.    
    14.   // tag 表示組件類型,不用類型的組件調(diào)用不同方法獲取 child 
    15.   switch(workInProgress.tag) { 
    16.     // 函數(shù)組件 
    17.     case FunctionComponent: 
    18.       return updateFunctionComponent(current, workInProgress, newProps) 
    19.     // Class組件 
    20.     case ClassComponent: 
    21.       return updateClassComponent(current, workInProgress, newProps) 
    22.     // DOM 原生組件(div、span、button……) 
    23.     case HostComponent: 
    24.       return updateHostComponent(current, workInProgress) 
    25.     // DOM 文本組件 
    26.     case HostText: 
    27.       return updateHostText(current, workInProgress) 
    28.   } 

    首先判斷 current(即:workInProgress.alternate) 是否存在,如果存在表示需要更新,不存在就是首次加載,didReceiveUpdate 變量設(shè)置為 false,didReceiveUpdate 變量用于標(biāo)記是否需要調(diào)用 render 新建 fiber.child,如果為 false 就會重新構(gòu)建fiber.child,否則復(fù)用之前的 fiber.child。

    然后會依據(jù) workInProgress.tag 調(diào)用不同的方法構(gòu)建 fiber.child。關(guān)于 workInProgress.tag 的含義可以參考 react/packages/shared/ReactWorkTags.js,主要是用來區(qū)分每個節(jié)點各自的類型,下面是常用的幾個:

     
     
     
     
    1. var FunctionComponent = 0; // 函數(shù)組件 
    2. var ClassComponent = 1; // Class組件 
    3. var HostComponent = 5; // 原生組件 
    4. var HostText = 6; // 文本組件 

    調(diào)用的方法不一一展開講解,我們只看看 updateClassComponent:

     
     
     
     
    1. // 更新 class 組件 
    2. function updateClassComponent(current, workInProgress, newProps) { 
    3.   // 更新 state,省略了一萬行代碼,只保留了核心邏輯,看看就好 
    4.   var oldState = workInProgress.memoizedState 
    5.   var newState = oldState 
    6.  
    7.   var queue = workInProgress.updateQueue 
    8.   var pendingQueue = queue.shared.pending 
    9.   var firstUpdate = pendingQueue 
    10.   var update = pendingQueue 
    11.  
    12.   do { 
    13.     // 合并 state 
    14.     var partialState = update.payload 
    15.     newState = Object.assign({}, newState, partialState) 
    16.  
    17.     // 鏈表遍歷完畢 
    18.     update = update.next 
    19.     if (update === firstUpdate) { 
    20.      // 鏈表遍歷完畢 
    21.       queue.shared.pending = null 
    22.       break 
    23.     } 
    24.   } while (true) 
    25.  
    26.  workInProgress.memoizedState = newState // state 更新完畢 
    27.    
    28.   // 檢測 oldState 和 newState 是否一致,如果一致,跳過更新 
    29.   // 調(diào)用 componentWillUpdate 判斷是否需要更新 
    30.    
    31.  
    32.   var instance = workInProgress.stateNode 
    33.   instance.props = newProps 
    34.   instance.state = newState 
    35.  
    36.   // 調(diào)用 Component 實例的 render 
    37.   var nextChildren = instance.render() 
    38.   reconcileChildren(current, workInProgress, nextChildren) 
    39.   return workInProgress.child 

    首先遍歷了之前提到的 updateQueue 更新 state,然后就是判斷 state 是否更新,以此來推到組件是否需要更新(這部分代碼省略了),最后調(diào)用的組件 render 方法生成子組件的虛擬 DOM。最后的 reconcileChildren 就是依據(jù) render 的返回值來生成 fiber 節(jié)點并掛載到 workInProgress.child 上。

     
     
     
     
    1. // 構(gòu)造子節(jié)點 
    2. function reconcileChildren(current, workInProgress, nextChildren) { 
    3.   if (current === null) { 
    4.     workInProgress.child = mountChildFibers( 
    5.       workInProgress, null, nextChildren 
    6.     ) 
    7.   } else { 
    8.     workInProgress.child = reconcileChildFibers( 
    9.       workInProgress, current.child, nextChildren 
    10.     ) 
    11.   } 
    12.  
    13. // 兩個方法本質(zhì)上一樣,只是一個需要生成新的 fiber,一個復(fù)用之前的 
    14. var reconcileChildFibers = ChildReconciler(true) 
    15. var mountChildFibers = ChildReconciler(false) 
    16.  
    17. function ChildReconciler(shouldTrackSideEffects) { 
    18.   return function (returnFiber, currentChild, nextChildren) { 
    19.     // 不同類型進(jìn)行不同的處理 
    20.     // 返回對象 
    21.     if (typeof newChild === 'object' && newChild !== null) { 
    22.    return placeSingleChild( 
    23.         reconcileSingleElement( 
    24.           returnFiber, currentChild, newChild 
    25.         ) 
    26.       ) 
    27.     } 
    28.     // 返回數(shù)組 
    29.     if (Array.isArray(newChild)) { 
    30.       // ... 
    31.     } 
    32.     // 返回字符串或數(shù)字,表明是文本節(jié)點 
    33.     if ( 
    34.       typeof newChild === 'string' || 
    35.       typeof newChild === 'number' 
    36.     ) { 
    37.       // ... 
    38.     } 
    39.     // 返回 null,直接刪除節(jié)點 
    40.     return deleteRemainingChildren(returnFiber, currentChild) 
    41.   } 

    篇幅有限,看看 render 返回值為對象的情況(通常情況下,render 方法 return 的如果是 jsx 都會被轉(zhuǎn)化為虛擬 DOM,而虛擬 DOM 必定是對象或數(shù)組):

     
     
     
     
    1. if (typeof newChild === 'object' && newChild !== null) { 
    2.   return placeSingleChild( 
    3.     // 構(gòu)造 fiber,或者是復(fù)用 fiber 
    4.     reconcileSingleElement( 
    5.       returnFiber, currentChild, newChild 
    6.     ) 
    7.   ) 
    8.  
    9. function placeSingleChild(newFiber) { 
    10.   // 更新操作,需要設(shè)置 effectTag 
    11.   if (shouldTrackSideEffects && newFiber.alternate === null) { 
    12.     newFiber.effectTag = Placement 
    13.   } 
    14.   return newFiber 

    歸(completeWork)

    當(dāng) fiber.child 為空時,就會進(jìn)入 completeWork 流程。而 completeWork 主要就是收集 beginWork 階段設(shè)置的 effectTag,如果有設(shè)置 effectTag 就表明該節(jié)點發(fā)生了變更, effectTag 的主要類型如下(默認(rèn)為 NoEffect ,表示節(jié)點無需進(jìn)行操作,完整的定義可以參考 react/packages/shared/ReactSideEffectTags.js):

     
     
     
     
    1. export const NoEffect = /*                     */ 0b000000000000000; 
    2. export const PerformedWork = /*                */ 0b000000000000001; 
    3.  
    4. // You can change the rest (and add more). 
    5. export const Placement = /*                    */ 0b000000000000010; 
    6. export const Update = /*                       */ 0b000000000000100; 
    7. export const PlacementAndUpdate = /*           */ 0b000000000000110; 
    8. export const Deletion = /*                     */ 0b000000000001000; 
    9. export const ContentReset = /*                 */ 0b000000000010000; 
    10. export const Callback = /*                     */ 0b000000000100000; 
    11. export const DidCapture = /*                   */ 0b000000001000000; 

    我們看看 completeWork 過程中,具體進(jìn)行了哪些操作:

     
     
     
     
    1. function completeWork(current, workInProgress) { 
    2.   switch (workInProgress.tag) { 
    3.     // 這些組件沒有反應(yīng)到 DOM 的 effect,跳過處理 
    4.     case Fragment: 
    5.     case MemoComponent: 
    6.     case LazyComponent: 
    7.     case ContextConsumer: 
    8.     case FunctionComponent: 
    9.       return null 
    10.     // class 組件 
    11.     case ClassComponent: { 
    12.       // 處理 context 
    13.       var Component = workInProgress.type 
    14.       if (isContextProvider(Component)) { 
    15.         popContext(workInProgress) 
    16.       } 
    17.       return null 
    18.     } 
    19.     case HostComponent: { 
    20.       // 這里 Fiber 的 props 對應(yīng)的就是 DOM 節(jié)點的 props 
    21.       // 例如:id、src、className …… 
    22.     var newProps = workInProgress.pendingProps // props 
    23.       if ( 
    24.         current !== null && 
    25.         workInProgress.stateNode != null 
    26.       ) { // current 不為空,表示是更新操作 
    27.         var type = workInProgress.type 
    28.         updateHostComponent(current, workInProgress, type, newProps) 
    29.       } else { // current 為空,表示需要渲染 DOM 節(jié)點 
    30.         // 實例化 DOM,掛載到 fiber.stateNode 
    31.         var instance = createInstance(type, newProps) 
    32.         appendAllChildren(instance, workInProgress, false, false); 
    33.         workInProgress.stateNode = instance 
    34.       } 
    35.       return null 
    36.     } 
    37.     case HostText: { 
    38.       var newText = workInProgress.pendingProps // props 
    39.       if (current && workInProgress.stateNode != null) { 
    40.         var oldText = current.memoizedProps 
    41.         // 更新文本節(jié)點 
    42.         updateHostText(current, workInProgress, oldText, newText) 
    43.       } else { 
    44.         // 實例文本節(jié)點 
    45.         workInProgress.stateNode = createTextInstance(newText) 
    46.       } 
    47.       return null 
    48.     } 
    49.   } 

    與 beginWork 一樣,completeWork 過程中也會依據(jù) workInProgress.tag 來進(jìn)行不同的處理,其他類型的組件基本可以略過,只用關(guān)注下 HostComponent、HostText,這兩種類型的節(jié)點會反應(yīng)到真實 DOM 中,所以會有所處理。

     
     
     
     
    1. updateHostComponent = function ( 
    2.  current, workInProgress, type, newProps 
    3. ) { 
    4.   var oldProps = current.memoizedProps 
    5.  
    6.   if (oldProps === newProps) { 
    7.     // 新舊 props 無變化 
    8.     return 
    9.   } 
    10.  
    11.   var instance = workInProgress.stateNode // DOM 實例 
    12.   // 對比新舊 props 
    13.  var updatePayload = diffProperties(instance, type, oldProps, newProps) 
    14.   // 將發(fā)生變化的屬性放入 updateQueue 
    15.   // 注意這里的 updateQueue 不同于 Class 組件對應(yīng)的 fiber.updateQueue 
    16.   workInProgress.updateQueue = updatePayload 
    17. }; 

    updateHostComponent 方法最后會通過 diffProperties 方法獲取一個更新隊列,掛載到 fiber.updateQueue 上,這里的 updateQueue 不同于 Class 組件對應(yīng)的 fiber.updateQueue,不是一個鏈表結(jié)構(gòu),而是一個數(shù)組結(jié)構(gòu),用于更新真實 DOM。

    下面舉一個例子,修改 App 組件的 state 后,下面的 span 標(biāo)簽對應(yīng)的 data-val、style、children 都會相應(yīng)的發(fā)生修改,同時,在控制臺打印出 updatePayload 的結(jié)果。

     
     
     
     
    1. import React from 'react' 
    2.  
    3. class App extends React.Component { 
    4.   state = { val: 1 } 
    5.   clickBtn = () => { 
    6.     this.setState({ val: this.state.val + 1 }) 
    7.   } 
    8.   render() { 
    9.     return (
       
    10.       add 
    11.       
    12.         data-val={this.state.val} 
    13.         style={{ fontSize: this.state.val * 15 }} 
    14.       > 
    15.         { this.state.val } 
    16.        
    17.     
  •   } 
  •  
  • export default App 
  • console

    副作用鏈表

    在最后的更新階段,為了不用遍歷所有的節(jié)點,在 completeWork 過程結(jié)束后,會構(gòu)造一個 effectList 連接所有 effectTag 不為 NoEffect 的節(jié)點,在 commit 階段能夠更高效的遍歷節(jié)點。

     
     
     
     
    1. function completeUnitOfWork() { 
    2.   let completedWork = workInProgress 
    3.   while (completedWork !== null) { 
    4.     // 調(diào)用 completeWork()... 
    5.  
    6.     // 構(gòu)造 Effect List 過程 
    7.     var returnFiber = completedWork.return 
    8.     if (returnFiber !== null) { 
    9.       if (returnFiber.firstEffect === null) { 
    10.         returnFiber.firstEffect = completedWork.firstEffect; 
    11.       }  分享題目:React 架構(gòu)的演變 - 更新機制
      URL標(biāo)題:http://m.5511xx.com/article/djjcpip.html