React 架構的演變 - 更新機制

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

Fiber 數據結構

咱們先回顧一下 Fiber 節點的數據結構(以前文章省略了一部分屬性,因此和以前文章略有不一樣):git

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

    // 子節點
  this.child = null
  // 父節點
  this.return = null 
  // 兄弟節點
  this.sibling = null
  
  // 更新隊列,用於暫存 setState 的值
  this.updateQueue = null
  // 新傳入的 props
  this.pendingProps = pendingProps;
  // 以前的 props
  this.memoizedProps = null;
  // 以前的 state
  this.memoizedState = null;

  // 節點更新過時時間,用於時間分片
  // react 17 改成:lanes、childLanes
  this.expirationTime = NoLanes
  this.childExpirationTime = NoLanes

  // 對應到頁面的真實 DOM 節點
  this.stateNode = null
  // Fiber 節點的副本,能夠理解爲備胎,主要用於提高更新的性能
  this.alternate = null

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

緩存機制

能夠注意到 Fiber 節點有個 alternate 屬性,該屬性在節點初始化的時候默認爲空(this.alternate = null)。這個節點的做用就是用來緩存以前的 Fiber 節點,更新的時候會判斷 fiber.alternate 是否爲空來肯定當前是首次渲染仍是更新。下面咱們上代碼:github

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。後續調用的 render 方法,就是將傳入的組件掛載到 FiberRootNode.current(即 RootFiber) 的空 Fiber 節點上。數組

// 實驗版本對外暴露的 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 節點
  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 爲空,則建立 workInProgress
  if (workInProgressRoot !== root) {
    createWorkInProgress()
  }
}
function createWorkInProgress() {
  workInProgressRoot = root
  var current = root.current
  var workInProgress = current.alternate;
  if (workInProgress === null) {
    // 第一次構建,須要建立副本
    workInProgress = createFiber(current.tag)
    workInProgress.alternate = current
    current.alternate = workInProgress
  } else {
    // 更新過程能夠複用
    workInProgress.nextEffect = null
    workInProgress.firstEffect = null
    workInProgress.lastEffect = null
  }
}

開始更新時,若是 workInProgress 爲空會指向一個新的空 Fiber 節點,表示正在進行工做的 Fiber 節點。數據結構

workInProgress.alternate = current
current.alternate = workInProgress

fiber tree

構造好 workInProgress 以後,就會開始在新的 RootFiber 下生成新的子 Fiber 節點了。架構

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)
  // 省略後續代碼...
}

按照咱們前面的案例, workLoopConcurrent 調用完成後,最後獲得的 fiber 樹以下:app

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

fiber tree

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

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 樹
}

fiber tree

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

function createWorkInProgress() {
  workInProgressRoot = root
  var current = root.current
  var workInProgress = current.alternate;
  if (workInProgress === null) {
    // 第一次構建,須要建立副本
    workInProgress = createFiber(current.tag)
    workInProgress.alternate = current
    current.alternate = workInProgress
  } else {
    // 更新過程能夠複用
    workInProgress.nextEffect = null
    workInProgress.firstEffect = null
    workInProgress.lastEffect = null
  }
}

fiber tree

在後續的遍歷過程當中(workLoopConcurrent()),會在舊的 RootFiber 下構建一個新的 fiber tree,而且每一個 fiber 節點的 alternate 都會指向 current fiber tree 下的節點。

fiber 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 節點都有一個 updateQueue 屬性,這個屬性就是用來緩存 setState 值的,只是結構從 React 15 的數組變成了鏈表結構。

不管是首次 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 節點上。

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 上造成一個單向循環鏈表,具體例子更形象的展現下這個鏈表結構。

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 以後,會連續進行三次 setState,每次 setState 都會更新 updateQueue。

第一次 setState

第二次 setState

第三次 setState

更新過程當中,咱們遍歷下 updateQueue 鏈表,能夠看到結果與預期的一致。

let $pending = sharedQueue.pending
// 遍歷鏈表,在控制檯輸出 payload
while($pending) {
  console.log('update.payload', $pending.payload)
  $pending = $pending.next
}

鏈表數據

遞歸 Fiber 節點

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

  • beginWork:生成新的 state,調用 render 建立子節點,鏈接當前節點與子節點;
  • completeWork:依據 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() {
  // 向上回溯節點
  let completedWork = workInProgress
  while (completedWork !== null) {
    // 收集反作用,主要是用於標記節點是否須要操做 DOM
    var current = completedWork.alternate
    completeWork(current, completedWork)

    // 省略構造 Effect List 過程

    // 獲取 Fiber.sibling
    let siblingFiber = workInProgress.sibling
    if (siblingFiber) {
      // sibling 存在,則跳出 complete 流程,繼續 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 發生變化,或者 type 發生變化
    if (oldProps !== newProps || workInProgress.type !== current.type) {
      // 設置更新標誌位爲 true
      didReceiveUpdate = true
    }
  } else { // current 爲空表示首次加載,須要進行 mount
    didReceiveUpdate = false
  }
  
  // tag 表示組件類型,不用類型的組件調用不一樣方法獲取 child
  switch(workInProgress.tag) {
    // 函數組件
    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

而後會依據 workInProgress.tag 調用不一樣的方法構建 fiber.child。關於 workInProgress.tag 的含義能夠參考 react/packages/shared/ReactWorkTags.js,主要是用來區分每一個節點各自的類型,下面是經常使用的幾個:

var FunctionComponent = 0; // 函數組件
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 就是依據 render 的返回值來生成 fiber 節點並掛載到 workInProgress.child 上。

// 構造子節點
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
        )
      )
    }
    // 返回數組
    if (Array.isArray(newChild)) {
      // ...
    }
    // 返回字符串或數字,代表是文本節點
    if (
      typeof newChild === 'string' ||
      typeof newChild === 'number'
    ) {
      // ...
    }
    // 返回 null,直接刪除節點
    return deleteRemainingChildren(returnFiber, currentChild)
  }
}

篇幅有限,看看 render 返回值爲對象的狀況(一般狀況下,render 方法 return 的若是是 jsx 都會被轉化爲虛擬 DOM,而虛擬 DOM 一定是對象或數組):

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 就代表該節點發生了變動, effectTag 的主要類型以下(默認爲 NoEffect ,表示節點無需進行操做,完整的定義能夠參考 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 節點的 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 節點
        // 實例化 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
        // 更新文本節點
        updateHostText(current, workInProgress, oldText, newText)
      } else {
        // 實例文本節點
        workInProgress.stateNode = createTextInstance(newText)
      }
      return null
    }
  }
}

beginWork 同樣,completeWork 過程當中也會依據 workInProgress.tag 來進行不一樣的處理,其餘類型的組件基本能夠略過,只用關注下 HostComponentHostText,這兩種類型的節點會反應到真實 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)
  // 將發生變化的屬性放入 updateQueue
  // 注意這裏的 updateQueue 不一樣於 Class 組件對應的 fiber.updateQueue
  workInProgress.updateQueue = updatePayload
};

updateHostComponent 方法最後會經過 diffProperties 方法獲取一個更新隊列,掛載到 fiber.updateQueue 上,這裏的 updateQueue 不一樣於 Class 組件對應的 fiber.updateQueue,不是一個鏈表結構,而是一個數組結構,用於更新真實 DOM。

下面舉一個例子,修改 App 組件的 state 後,下面的 span 標籤對應的 data-valstylechildren 都會相應的發生修改,同時,在控制檯打印出 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}>add</button>
      <span
        data-val={this.state.val}
        style={{ fontSize: this.state.val * 15 }}
      >
        { this.state.val }
      </span>
    </div>)
  }
}

export default App

console

反作用鏈表

在最後的更新階段,爲了避免用遍歷全部的節點,在 completeWork 過程結束後,會構造一個 effectList 鏈接全部 effectTag 不爲 NoEffect 的節點,在 commit 階段可以更高效的遍歷節點。

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>
  }
}

App

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

第一個 td 完成 completeWork 後,EffectList 結果以下:

1

第二個 td 完成 completeWork 後,EffectList 結果以下:

2

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

3

後面兩個 td 結束 completeWork 流程後,EffectList 結果以下:

4

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

5

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

6

提交更新

這一階段的主要做用就是遍歷 effectList 裏面的節點,將更新反應到真實 DOM 中,固然還涉及一些生命週期鉤子的調用,咱們這裏只展現最簡單的邏輯。

function commitRoot(root) {
  var finishedWork = root.finishedWork
  var firstEffect = finishedWork
  var nextEffect = firstEffect
  // 遍歷effectList
  while (nextEffect !== null) {
    const effectTag = nextEffect.effectTag
    // 根據 effectTag 進行不一樣的處理
    switch (effectTag) {
      // 插入 DOM 節點
      case Placement: {
        commitPlacement(nextEffect)
        nextEffect.effectTag &= ~Placement
        break
      }
      // 更新 DOM 節點
      case Update: {
        const current = nextEffect.alternate
        commitWork(current, nextEffect)
        break
      }
      // 刪除 DOM 節點
      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 架構下,全部數據結構都是鏈表形式,鏈表的遍歷都是經過循環的方式來實現的,看代碼的過程當中常常會被忽然出現的 return、break 擾亂思路,因此要徹底理解這個流程仍是很不容易的。

最後,但願你們在閱讀文章的過程當中能有收穫,下一篇文章會開始寫 Hooks 相關的內容。
image

相關文章
相關標籤/搜索