<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React 架構的演變 - 更新機制

          共 19783字,需瀏覽 40分鐘

           ·

          2020-11-06 10:22

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

          Fiber 數(shù)據(jù)結構

          我們先回顧一下 Fiber 節(jié)點的數(shù)據(jù)結構(之前文章省略了一部分屬性,所以和之前文章略有不同):

          function?FiberNode?(tag,?key)?{
          ??//?節(jié)點?key,主要用于了優(yōu)化列表?diff
          ??this.key?=?key
          ??//?節(jié)點類型;FunctionComponent:?0, ClassComponent: 1, HostRoot: 3 ...
          ??this.tag?=?tag

          ?//?子節(jié)點
          ??this.child?=?null
          ??//?父節(jié)點
          ??this.return?=?null?
          ??//?兄弟節(jié)點
          ??this.sibling?=?null
          ??
          ??//?更新隊列,用于暫存?setState?的值
          ??this.updateQueue?=?null
          ??//?新傳入的?props
          ??this.pendingProps?=?pendingProps;
          ??//?之前的?props
          ??this.memoizedProps?=?null;
          ??//?之前的?state
          ??this.memoizedState?=?null;

          ??//?節(jié)點更新過期時間,用于時間分片
          ??// react 17 改為:lanes、childLanes
          ??this.expirationTime?=?NoLanes
          ??this.childExpirationTime?=?NoLanes

          ??//?對應到頁面的真實?DOM?節(jié)點
          ??this.stateNode?=?null
          ??//?Fiber?節(jié)點的副本,可以理解為備胎,主要用于提升更新的性能
          ??this.alternate?=?null

          ??//?副作用相關,用于標記節(jié)點是否需要更新
          ??//?以及更新的類型:替換成新節(jié)點、更新屬性、更新文本、刪除……
          ??this.effectTag?=?NoEffect
          ??//?指向下一個需要更新的節(jié)點
          ??this.nextEffect?=?null
          ??this.firstEffect?=?null
          ??this.lastEffect?=?null
          }

          緩存機制

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

          import?React?from?'react';
          import?ReactDOM?from?'react-dom';

          class?App?extends?React.Component?{
          ??state?=?{?val:?0?}
          ??render()?{
          ????return?<div>val:?{?this.state.val?}div>
          ??}
          }

          ReactDOM.unstable_createRoot(
          ??document.getElementById('root')
          ).render(<App?/>)

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

          //?實驗版本對外暴露的?createRoot?需要加上?`unstable_`?前綴
          exports.unstable_createRoot?=?createRoot

          function?createRoot(container)?{
          ??return?new?ReactDOMRoot(container)
          }
          function?ReactDOMRoot(container)?{
          ??var?root?=?new?FiberRootNode()
          ??//?createRootFiber?=>?createFiber?=>?return?new?FiberNode(tag);
          ??root.current?=?createRootFiber()?//?掛載一個空的?fiber?節(jié)點
          ??this._internalRoot?=?root
          }
          ReactDOMRoot.prototype.render?=?function?render(children)?{
          ??var?root?=?this._internalRoot
          ??var?update?=?createUpdate()
          ??update.payload?=?{?element:?children?}
          ??const?rootFiber?=?root.current
          ??//?update對象放到?rootFiber?的?updateQueue?中
          ??enqueueUpdate(rootFiber,?update)
          ??//?開始更新流程
          ??scheduleUpdateOnFiber(rootFiber)
          }

          render 最后調用 scheduleUpdateOnFiber 進入更新任務,該方法之前有說明,最后會通過 scheduleCallback 走 MessageChannel 消息進入下個任務隊列,最后調用 performConcurrentWorkOnRoot 方法。

          //?scheduleUpdateOnFiber
          //?=>?ensureRootIsScheduled
          //?=>?scheduleCallback(performConcurrentWorkOnRoot)
          function?performConcurrentWorkOnRoot(root)?{
          ??renderRootConcurrent(root)
          }
          function?renderRootConcurrent(root)?{
          ??//?workInProgressRoot?為空,則創(chuàng)建?workInProgress
          ??if?(workInProgressRoot?!==?root)?{
          ????createWorkInProgress()
          ??}
          }
          function?createWorkInProgress()?{
          ??workInProgressRoot?=?root
          ??var?current?=?root.current
          ??var?workInProgress?=?current.alternate;
          ??if?(workInProgress?===?null)?{
          ????//?第一次構建,需要創(chuàng)建副本
          ????workInProgress?=?createFiber(current.tag)
          ????workInProgress.alternate?=?current
          ????current.alternate?=?workInProgress
          ??}?else?{
          ????//?更新過程可以復用
          ????workInProgress.nextEffect?=?null
          ????workInProgress.firstEffect?=?null
          ????workInProgress.lastEffect?=?null
          ??}
          }

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

          workInProgress.alternate?=?current
          current.alternate?=?workInProgress
          a8afaecd537a133b453671ce9c862927.webpfiber tree

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

          function?renderRootConcurrent(root)?{
          ??//?構造?workInProgress...
          ??//?workInProgress.alternate?=?current
          ?//?current.alternate?=?workInProgress

          ??//?進入遍歷?fiber?樹的流程
          ??workLoopConcurrent()
          }

          function?workLoopConcurrent()?{
          ??while?(workInProgress?!==?null?&&?!shouldYield())?{
          ????performUnitOfWork()
          ??}
          }

          function?performUnitOfWork()?{
          ??var?current?=?workInProgress.alternate
          ??//?返回當前?Fiber?的?child
          ??const?next?=?beginWork(current,?workInProgress)
          ??//?省略后續(xù)代碼...
          }

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

          class?App?extends?React.Component?{
          ??state?=?{?val:?0?}
          ??render()?{
          ????return?<div>val:?{?this.state.val?}div>
          ??}
          }
          56dd48aa41567d4b88159de0496db199.webpfiber tree

          最后進入 Commit 階段的時候,會切換 FiberRootNode 的 current 屬性:

          function?performConcurrentWorkOnRoot()?{
          ??renderRootConcurrent()?//?結束遍歷流程,fiber?tree?已經構造完畢

          ??var?finishedWork?=?root.current.alternate
          ??root.finishedWork?=?finishedWork
          ??commitRoot(root)
          }
          function?commitRoot()?{
          ??var?finishedWork?=?root.finishedWork
          ??root.finishedWork?=?null
          ??root.current?=?finishedWork?//?切換到新的?fiber?樹
          }
          c590c884a03d230579317b574dbbecd9.webpfiber tree

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

          function?createWorkInProgress()?{
          ??workInProgressRoot?=?root
          ??var?current?=?root.current
          ??var?workInProgress?=?current.alternate;
          ??if?(workInProgress?===?null)?{
          ????//?第一次構建,需要創(chuàng)建副本
          ????workInProgress?=?createFiber(current.tag)
          ????workInProgress.alternate?=?current
          ????current.alternate?=?workInProgress
          ??}?else?{
          ????//?更新過程可以復用
          ????workInProgress.nextEffect?=?null
          ????workInProgress.firstEffect?=?null
          ????workInProgress.lastEffect?=?null
          ??}
          }
          45483b4cc92a31eaac1a67df39fff168.webpfiber tree

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

          48f4a8d3aa3b90d6e5bdde5015fdd226.webpfiber tree

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

          更新隊列

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

          //?setState?方法掛載到原型鏈上
          ReactComponent.prototype.setState?=?function?(partialState,?callback)?{
          ??//?調用?setState?后,會調用內部的?updater.enqueueSetState
          ??this.updater.enqueueSetState(this,?partialState)
          };

          var?ReactUpdateQueue?=?{
          ??enqueueSetState(component,?partialState)?{
          ????//?在組件的?_pendingStateQueue?上暫存新的?state
          ????if?(!component._pendingStateQueue)?{
          ??????component._pendingStateQueue?=?[]
          ????}
          ????//?將?setState?的值放入隊列中
          ????var?queue?=?component._pendingStateQueue
          ????queue.push(partialState)
          ????enqueueUpdate(component)
          ??}
          }

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

          無論是首次 Render 的 Mount 階段,還是 setState 的 Update 階段,內部都會調用 enqueueUpdate 方法。

          //?---?Render?階段?---
          function?initializeUpdateQueue(fiber)?{
          ??var?queue?=?{
          ????baseState:?fiber.memoizedState,
          ????firstBaseUpdate:?null,
          ????lastBaseUpdate:?null,
          ????shared:?{
          ??????pending:?null
          ????},
          ????effects:?null
          ??}
          ??fiber.updateQueue?=?queue
          }
          ReactDOMRoot.prototype.render?=?function?render(children)?{
          ??var?root?=?this._internalRoot
          ??var?update?=?createUpdate()
          ??update.payload?=?{?element:?children?}
          ??const?rootFiber?=?root.current
          ??//?初始化?rootFiber?的?updateQueue
          ??initializeUpdateQueue(rootFiber)
          ??//?update?對象放到?rootFiber?的?updateQueue?中
          ??enqueueUpdate(rootFiber,?update)
          ??//?開始更新流程
          ??scheduleUpdateOnFiber(rootFiber)
          }

          //?---?Update?階段?---
          Component.prototype.setState?=?function?(partialState,?callback)?{
          ??this.updater.enqueueSetState(this,?partialState)
          }
          var?classComponentUpdater?=?{
          ??enqueueSetState:?function?(inst,?payload)?{
          ????//?獲取實例對應的fiber
          ????var?fiber?=?get(inst)
          ????var?update?=?createUpdate()
          ????update.payload?=?payload

          ????//?update?對象放到?rootFiber?的?updateQueue?中
          ????enqueueUpdate(fiber,?update)
          ????scheduleUpdateOnFiber(fiber)
          ??}
          }

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

          function?enqueueUpdate(fiber,?update)?{
          ??var?updateQueue?=?fiber.updateQueue;

          ??if?(updateQueue?===?null)?{
          ????//?updateQueue?為空則跳過
          ????return;
          ??}
          ??var?sharedQueue?=?updateQueue.shared;
          ??var?pending?=?sharedQueue.pending;

          ??if?(pending?===?null)?{
          ????update.next?=?update;
          ??}?else?{
          ????update.next?=?pending.next;
          ????pending.next?=?update;
          ??}

          ??sharedQueue.pending?=?update;
          }

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

          class?App?extends?React.Component?{
          ??state?=?{?val:?0?}
          ??click?()?{
          ????for?(let?i?=?0;?i?3;?i++)?{
          ??????this.setState({?val:?this.state.val?+?1?})
          ????}
          ??}
          ??render()?{
          ????return?<div?onClick={()?=>?{
          ??????this.click()
          ????}}>val:?{?this.state.val?}div>

          ??}
          }

          點擊 div 之后,會連續(xù)進行三次 setState,每次 setState 都會更新 updateQueue。

          af9caf7c36a74fbd7235566c9eef6bdc.webp第一次 setState2e773600b5ccb38e891a905eb190a6b3.webp第二次 setStatede594f87ab1926e71e4a85d37d19bb1e.webp第三次 setState

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

          let?$pending?=?sharedQueue.pending
          //?遍歷鏈表,在控制臺輸出?payload
          while($pending)?{
          ??console.log('update.payload',?$pending.payload)
          ??$pending?=?$pending.next
          }
          cd347190526cf289370ef31628574a6a.webp鏈表數(shù)據(jù)

          遞歸 Fiber 節(jié)點

          Fiber 架構下每個節(jié)點都會經歷遞(beginWork)歸(completeWork)兩個過程:

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

          先回顧下這個流程:

          function?workLoopConcurrent()?{
          ??while?(workInProgress?!==?null?&&?!shouldYield())?{
          ????performUnitOfWork()
          ??}
          }

          function?performUnitOfWork()?{
          ??var?current?=?workInProgress.alternate
          ??//?返回當前?Fiber?的?child
          ??const?next?=?beginWork(current,?workInProgress)
          ??if?(next?===?null)?{?//?child?不存在
          ????completeUnitOfWork()
          ??}?else?{?//?child?存在
          ????//?重置?workInProgress?為?child
          ????workInProgress?=?next
          ??}
          }
          function?completeUnitOfWork()?{
          ??//?向上回溯節(jié)點
          ??let?completedWork?=?workInProgress
          ??while?(completedWork?!==?null)?{
          ????//?收集副作用,主要是用于標記節(jié)點是否需要操作?DOM
          ????var?current?=?completedWork.alternate
          ????completeWork(current,?completedWork)

          ????//?省略構造?Effect?List?過程

          ????//?獲取?Fiber.sibling
          ????let?siblingFiber?=?workInProgress.sibling
          ????if?(siblingFiber)?{
          ??????//?sibling?存在,則跳出?complete?流程,繼續(xù)?beginWork
          ??????workInProgress?=?siblingFiber
          ??????return
          ????}

          ????completedWork?=?completedWork.return
          ????workInProgress?=?completedWork
          ??}
          }

          遞(beginWork)

          先看看 beginWork 進行了哪些操作:

          function?beginWork(current,?workInProgress)?{
          ??if?(current?!==?null)?{?//?current?不為空,表示需要進行?update
          ????var?oldProps?=?current.memoizedProps?//?原先傳入的?props
          ????var?newProps?=?workInProgress.pendingProps?//?更新過程中新的?props
          ????//?組件的?props?發(fā)生變化,或者?type?發(fā)生變化
          ????if?(oldProps?!==?newProps?||?workInProgress.type?!==?current.type)?{
          ??????//?設置更新標志位為?true
          ??????didReceiveUpdate?=?true
          ????}
          ??}?else?{?//?current?為空表示首次加載,需要進行?mount
          ????didReceiveUpdate?=?false
          ??}
          ??
          ??//?tag?表示組件類型,不用類型的組件調用不同方法獲取?child
          ??switch(workInProgress.tag)?{
          ????//?函數(shù)組件
          ????case?FunctionComponent:
          ??????return?updateFunctionComponent(current,?workInProgress,?newProps)
          ????//?Class組件
          ????case?ClassComponent:
          ??????return?updateClassComponent(current,?workInProgress,?newProps)
          ????//?DOM?原生組件(div、span、button……)
          ????case?HostComponent:
          ??????return?updateHostComponent(current,?workInProgress)
          ????//?DOM?文本組件
          ????case?HostText:
          ??????return?updateHostText(current,?workInProgress)
          ??}
          }

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

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

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

          調用的方法不一一展開講解,我們只看看 updateClassComponent

          //?更新?class?組件
          function?updateClassComponent(current,?workInProgress,?newProps)?{
          ??//?更新?state,省略了一萬行代碼,只保留了核心邏輯,看看就好
          ??var?oldState?=?workInProgress.memoizedState
          ??var?newState?=?oldState

          ??var?queue?=?workInProgress.updateQueue
          ??var?pendingQueue?=?queue.shared.pending
          ??var?firstUpdate?=?pendingQueue
          ??var?update?=?pendingQueue

          ??do?{
          ????//?合并?state
          ????var?partialState?=?update.payload
          ????newState?=?Object.assign({},?newState,?partialState)

          ????//?鏈表遍歷完畢
          ????update?=?update.next
          ????if?(update?===?firstUpdate)?{
          ?????//?鏈表遍歷完畢
          ??????queue.shared.pending?=?null
          ??????break
          ????}
          ??}?while?(true)

          ?workInProgress.memoizedState?=?newState?//?state?更新完畢
          ??
          ??//?檢測?oldState?和?newState?是否一致,如果一致,跳過更新
          ??//?調用?componentWillUpdate?判斷是否需要更新
          ??

          ??var?instance?=?workInProgress.stateNode
          ??instance.props?=?newProps
          ??instance.state?=?newState

          ??//?調用?Component?實例的?render
          ??var?nextChildren?=?instance.render()
          ??reconcileChildren(current,?workInProgress,?nextChildren)
          ??return?workInProgress.child
          }

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

          //?構造子節(jié)點
          function?reconcileChildren(current,?workInProgress,?nextChildren)?{
          ??if?(current?===?null)?{
          ????workInProgress.child?=?mountChildFibers(
          ??????workInProgress,?null,?nextChildren
          ????)
          ??}?else?{
          ????workInProgress.child?=?reconcileChildFibers(
          ??????workInProgress,?current.child,?nextChildren
          ????)
          ??}
          }

          //?兩個方法本質上一樣,只是一個需要生成新的?fiber,一個復用之前的
          var?reconcileChildFibers?=?ChildReconciler(true)
          var?mountChildFibers?=?ChildReconciler(false)

          function?ChildReconciler(shouldTrackSideEffects)?{
          ??return?function?(returnFiber,?currentChild,?nextChildren)?{
          ????//?不同類型進行不同的處理
          ????//?返回對象
          ????if?(typeof?newChild?===?'object'?&&?newChild?!==?null)?{
          ???return?placeSingleChild(
          ????????reconcileSingleElement(
          ??????????returnFiber,?currentChild,?newChild
          ????????)
          ??????)
          ????}
          ????//?返回數(shù)組
          ????if?(Array.isArray(newChild))?{
          ??????//?...
          ????}
          ????//?返回字符串或數(shù)字,表明是文本節(jié)點
          ????if?(
          ??????typeof?newChild?===?'string'?||
          ??????typeof?newChild?===?'number'
          ????)?{
          ??????//?...
          ????}
          ????//?返回?null,直接刪除節(jié)點
          ????return?deleteRemainingChildren(returnFiber,?currentChild)
          ??}
          }

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

          if?(typeof?newChild?===?'object'?&&?newChild?!==?null)?{
          ??return?placeSingleChild(
          ????//?構造?fiber,或者是復用?fiber
          ????reconcileSingleElement(
          ??????returnFiber,?currentChild,?newChild
          ????)
          ??)
          }

          function?placeSingleChild(newFiber)?{
          ??//?更新操作,需要設置?effectTag
          ??if?(shouldTrackSideEffects?&&?newFiber.alternate?===?null)?{
          ????newFiber.effectTag?=?Placement
          ??}
          ??return?newFiber
          }

          歸(completeWork)

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

          export?const?NoEffect?=?/*?????????????????????*/?0b000000000000000;
          export?const?PerformedWork?=?/*????????????????*/?0b000000000000001;

          //?You?can?change?the?rest?(and?add?more).
          export?const?Placement?=?/*????????????????????*/?0b000000000000010;
          export?const?Update?=?/*???????????????????????*/?0b000000000000100;
          export?const?PlacementAndUpdate?=?/*???????????*/?0b000000000000110;
          export?const?Deletion?=?/*?????????????????????*/?0b000000000001000;
          export?const?ContentReset?=?/*?????????????????*/?0b000000000010000;
          export?const?Callback?=?/*?????????????????????*/?0b000000000100000;
          export?const?DidCapture?=?/*???????????????????*/?0b000000001000000;

          我們看看 completeWork 過程中,具體進行了哪些操作:

          function?completeWork(current,?workInProgress)?{
          ??switch?(workInProgress.tag)?{
          ????//?這些組件沒有反應到?DOM?的?effect,跳過處理
          ????case?Fragment:
          ????case?MemoComponent:
          ????case?LazyComponent:
          ????case?ContextConsumer:
          ????case?FunctionComponent:
          ??????return?null
          ????//?class?組件
          ????case?ClassComponent:?{
          ??????//?處理?context
          ??????var?Component?=?workInProgress.type
          ??????if?(isContextProvider(Component))?{
          ????????popContext(workInProgress)
          ??????}
          ??????return?null
          ????}
          ????case?HostComponent:?{
          ??????//?這里?Fiber?的?props?對應的就是?DOM?節(jié)點的?props
          ??????//?例如:id、src、className ……
          ????var?newProps?=?workInProgress.pendingProps?//?props
          ??????if?(
          ????????current?!==?null?&&
          ????????workInProgress.stateNode?!=?null
          ??????)?{?//?current?不為空,表示是更新操作
          ????????var?type?=?workInProgress.type
          ????????updateHostComponent(current,?workInProgress,?type,?newProps)
          ??????}?else?{?//?current?為空,表示需要渲染?DOM?節(jié)點
          ????????//?實例化?DOM,掛載到?fiber.stateNode
          ????????var?instance?=?createInstance(type,?newProps)
          ????????appendAllChildren(instance,?workInProgress,?false,?false);
          ????????workInProgress.stateNode?=?instance
          ??????}
          ??????return?null
          ????}
          ????case?HostText:?{
          ??????var?newText?=?workInProgress.pendingProps?//?props
          ??????if?(current?&&?workInProgress.stateNode?!=?null)?{
          ????????var?oldText?=?current.memoizedProps
          ????????//?更新文本節(jié)點
          ????????updateHostText(current,?workInProgress,?oldText,?newText)
          ??????}?else?{
          ????????//?實例文本節(jié)點
          ????????workInProgress.stateNode?=?createTextInstance(newText)
          ??????}
          ??????return?null
          ????}
          ??}
          }

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

          updateHostComponent?=?function?(
          ?current,?workInProgress,?type,?newProps
          )?
          {
          ??var?oldProps?=?current.memoizedProps

          ??if?(oldProps?===?newProps)?{
          ????//?新舊?props?無變化
          ????return
          ??}

          ??var?instance?=?workInProgress.stateNode?//?DOM?實例
          ??//?對比新舊?props
          ?var?updatePayload?=?diffProperties(instance,?type,?oldProps,?newProps)
          ??//?將發(fā)生變化的屬性放入?updateQueue
          ??//?注意這里的?updateQueue?不同于?Class?組件對應的?fiber.updateQueue
          ??workInProgress.updateQueue?=?updatePayload
          };

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

          下面舉一個例子,修改 App 組件的 state 后,下面的 span 標簽對應的 data-valstylechildren 都會相應的發(fā)生修改,同時,在控制臺打印出 updatePayload 的結果。

          import?React?from?'react'

          class?App?extends?React.Component?{
          ??state?=?{?val:?1?}
          ??clickBtn?=?()?=>?{
          ????this.setState({?val:?this.state.val?+?1?})
          ??}
          ??render()?{
          ????return?(<div>
          ??????<button?onClick={this.clickBtn}>addbutton>

          ??????<span
          ????????data-val={this.state.val}
          ????????style={{?fontSize:?this.state.val?*?15?}}
          ??????>

          ????????{?this.state.val?}
          ??????span>
          ????div>)
          ??}
          }

          export?default?App
          4f059d283c639cedd9d64da434adf466.webpconsole

          副作用鏈表

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

          function?completeUnitOfWork()?{
          ??let?completedWork?=?workInProgress
          ??while?(completedWork?!==?null)?{
          ????//?調用?completeWork()...

          ????//?構造?Effect?List?過程
          ????var?returnFiber?=?completedWork.return
          ????if?(returnFiber?!==?null)?{
          ??????if?(returnFiber.firstEffect?===?null)?{
          ????????returnFiber.firstEffect?=?completedWork.firstEffect;
          ??????}
          ??????if?(completedWork.lastEffect?!==?null)?{
          ????????if?(returnFiber.lastEffect?!==?null)?{
          ??????????returnFiber.lastEffect.nextEffect?=?completedWork.firstEffect;
          ????????}
          ????????returnFiber.lastEffect?=?completedWork.lastEffect;
          ??????}

          ??????if?(completedWork.effectTag?>?PerformedWork)?{
          ????????if?(returnFiber.lastEffect?!==?null)?{
          ??????????returnFiber.lastEffect.nextEffect?=?completedWork
          ????????}?else?{
          ??????????returnFiber.firstEffect?=?completedWork
          ????????}
          ????????returnFiber.lastEffect?=?completedWork
          ??????}
          ????}

          ????//?判斷?completedWork.sibling?是否存在...
          ??}
          }

          上面的代碼就是構造 effectList 的過程,光看代碼還是比較難理解的,我們還是通過實際的代碼來解釋一下。

          import?React?from?'react'

          export?default?class?App?extends?React.Component?{
          ??state?=?{?val:?0?}
          ??click?=?()?=>?{
          ????this.setState({?val:?this.state.val?+?1?})
          ??}
          ??render()?{
          ????const?{?val?}?=?this.state
          ????const?array?=?Array(2).fill()
          ????const?rows?=?array.map(
          ??????(_,?row)?=>?<tr?key={row}>
          ????????{array.map(
          ??????????(_,?col)?=>?<td?key={col}>{val}td>

          ????????)}
          ??????tr>
          ????)
          ????return?<table?onClick={()?=>?this.click()}>
          ??????{rows}
          ????table>

          ??}
          }
          103a718eb35c37af0b59c1f281057cc2.webpApp

          我們構造一個 2 * 2 的 Table,每次點擊組件,td 的 children 都會發(fā)生修改,下面看看這個過程中的 effectList 是如何變化的。

          第一個 td 完成 completeWork 后,EffectList 結果如下:

          f6b189a8bef6755a2c08edbe267c4299.webp1

          第二個 td 完成 completeWork 后,EffectList 結果如下:

          19edb07b835ab3692c49274fa1955c31.webp2

          兩個 td 結束了 completeWork 流程,會回溯到 tr 進行 completeWork ,tr 結束流程后 ,table 會直接復用 tr 的 firstEffect 和 lastEffect,EffectList 結果如下:

          28ce0a2b1339628ee252e97cacaecccd.webp3

          后面兩個 td 結束 completeWork 流程后,EffectList 結果如下:

          4c16dfc8c2b3250bab9b81d937edacfc.webp4

          回溯到第二個 tr 進行 completeWork ,由于 table 已經存在 firstEffect 和 lastEffect,這里會直接修改 table 的 firstEffect 的 nextEffect,以及重新指定 lastEffect,EffectList 結果如下:

          9152de0cb7f5333b6b5d1a7e67c61d1d.webp5

          最后回溯到 App 組件時,就會直接復用 table 的 firstEffect 和 lastEffect,最后 的EffectList 結果如下:

          71f4c6f82c54b95e14c8285d641b29a6.webp6

          提交更新

          這一階段的主要作用就是遍歷 effectList 里面的節(jié)點,將更新反應到真實 DOM 中,當然還涉及一些生命周期鉤子的調用,我們這里只展示最簡單的邏輯。

          function?commitRoot(root)?{
          ??var?finishedWork?=?root.finishedWork
          ??var?firstEffect?=?finishedWork
          ??var?nextEffect?=?firstEffect
          ??//?遍歷effectList
          ??while?(nextEffect?!==?null)?{
          ????const?effectTag?=?nextEffect.effectTag
          ????//?根據(jù)?effectTag?進行不同的處理
          ????switch?(effectTag)?{
          ??????//?插入?DOM?節(jié)點
          ??????case?Placement:?{
          ????????commitPlacement(nextEffect)
          ????????nextEffect.effectTag?&=?~Placement
          ????????break
          ??????}
          ??????//?更新?DOM?節(jié)點
          ??????case?Update:?{
          ????????const?current?=?nextEffect.alternate
          ????????commitWork(current,?nextEffect)
          ????????break
          ??????}
          ??????//?刪除?DOM?節(jié)點
          ??????case?Deletion:?{
          ????????commitDeletion(root,?nextEffect)
          ????????break
          ??????}
          ????}
          ????nextEffect?=?nextEffect.nextEffect
          ??}
          }

          這里不再展開講解每個 effect 下具體的操作,在遍歷完 effectList 之后,就是將當前的 fiber 樹進行切換。

          function?commitRoot()?{
          ??var?finishedWork?=?root.finishedWork

          ??//?遍歷?effectList?……

          ??root.finishedWork?=?null
          ??root.current?=?finishedWork?//?切換到新的?fiber?樹
          }

          總結

          到這里整個更新流程就結束了,可以看到 Fiber 架構下,所有數(shù)據(jù)結構都是鏈表形式,鏈表的遍歷都是通過循環(huán)的方式來實現(xiàn)的,看代碼的過程中經常會被突然出現(xiàn)的 return、break 擾亂思路,所以要完全理解這個流程還是很不容易的。

          最后,希望大家在閱讀文章的過程中能有收獲,下一篇文章會開始寫 Hooks 相關的內容。

          ●?前端入門機器學習 Tensorflow.js 簡明教程

          ●?簡單代碼的秘訣

          ●?【讀懂源碼】React 架構的演變 - 從同步到異步



          ·END·

          圖雀社區(qū)

          匯聚精彩的免費實戰(zhàn)教程



          關注公眾號回復 z 拉學習交流群


          喜歡本文,點個“在看”告訴我

          b85ca38a6ae1c08f31a259c85e642f9a.webp


          瀏覽 42
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色在线免费一级视频 | 无码在线观看免费视频 | 亚洲综合777777 | 7799精品视频天天看 | 欧美综合一区二区 |