「React 16」為 Luy 完成 React Fiber 架構

媒介

Facebook 的研發才能真是驚人, Fiber 架構給 React 帶來了新視野的同時,將調理一詞引見給了前端,然則這個架構着實不好懂,比起之前的 Vdom 樹,新的 Fiber 樹就貧苦太多。

可以說,React 16 和 React 15 已是技能上的分水嶺,然則得益於 React 16 的 Fiber 架構,使得 React 縱然在沒有開啟異步的狀況下,機能照舊是得到了進步。

經由兩個禮拜的痛楚研討,終究將 React 16 的襯着頭緒摸得比較清楚,可以寫文章來紀錄、回憶一下。

假如你已輕微明白了 Fiber 架構,可以直接看代碼:堆棧地點

什麼是 React Fiber ?

React Fiber 並非所謂的纖程(微線程、協程),而是一種基於瀏覽器的單線程調理算法,背地的支撐 API 是赫赫有名的: requestIdleCallback ,得到了這個 API 的支撐,我們便可以將 React 中最耗時的部份放入个中。

回憶 React 歷年來的算法都曉得,reconcilation 算法實際上是一個大遞歸,大遞歸一旦舉行,想要中綴照樣比較不好操縱的,加上頭大尾大的 React 15 代碼已膨脹到了難以想象的田地,在重重壓力之下,React 運用了大輪迴來替代之前的大遞歸,雖然代碼變得比遞歸難懂了幾個梯度,然則實際上,代碼量比本來少了異常多(開闢版本 3W 行緊縮到了 1.3W 行)

那題目就來了,什麼是 Fiber :一種將 recocilation (遞歸 diff ),拆分紅無數個小使命的算法;它隨時可以住手,恢復。住手恢復的機遇取決於當前的一幀( 16ms )內,另有無充足的時刻許可盤算。

《「React 16」為 Luy 完成 React Fiber 架構》

React 異步襯着流程圖

《「React 16」為 Luy 完成 React Fiber 架構》

  1. 用戶挪用 ReactDOM.render 要領,傳入比方<App />組件,React 最先運作<App />
  2. <App /> 在內部會被轉換成 RootFiber 節點,一個特別的節點,並紀錄在一個全局變量中,TopTree
  3. 拿到 <App />RootFiber ,起首建立一個 <App /> 對應的 Fiber ,然後加上 Fiber 信息,以便今後回溯。隨後,賦值給之前的全局變量 TopTree
  4. 運用 requestIdleCallback 重複第三個步驟,直到輪迴到樹的一切節點
  5. 末了完成了 diff 階段,一次性將變化更新到實在 DOM 中,以防備 UI 展現的不一連性

个中,重點就是 34 階段,這兩個階段將建立實在 DOM 和組件襯着 ( render )拆分為無數的小碎塊,運用 requestIdleCallback 一連舉行。在 React 15 的時刻,襯着、建立、插進去、刪除等操縱是最費時的,在 React 16 中將襯着、建立抽離出來分片,如許機能就得到了極大的提拔。

那為何更新到實在 DOM 中不能拆分呢?理論上來講,是可以拆分的,然則這會形成 UI 的不一連性,極大的影響體驗。

遞歸變成了輪迴

《「React 16」為 Luy 完成 React Fiber 架構》

以簡樸的組件為例子:

  1. 從頂端的 div#root 向下走,先走左子樹
  2. div 有兩個孩子 span ,繼承走左側的
  3. 來到 span ,之下只要一個 hello ,到此,不再繼承往下,而是往上回到 span
  4. 由於 span 有一個兄弟,因而往兄弟 span 走去
  5. 兄弟 span 有孩子 luy ,到此,不繼承往下,而是回到 luy 的老爹 span
  6. luy 的老爹 span 右側沒有兄弟了,因而回到其老爹 div
  7. div 沒有任何的兄弟,因而回到頂端的 div#root

每經由一個 Fiber 節點,實行 render 或許 document.createElement (或許更新 DOM )的操縱

Fiber 數據結構

一個 Fiber 數據結構比較龐雜

const Fiber = {
  tag: HOST_COMPONENT,
  type: 'div',
  return: parentFiber,
  child: childFiber,
  sibling: null,
  alternate: currentFiber,
  stateNode: document.createElement('div') | instance,
  props: { children: [], className: 'foo' },
  partialState: null,
  effectTag: PLACEMENT,
  effects: []
}

這是一個比較完整的 Fiber object,他龐雜的緣由是由於一個 Fiber 就代表了一個「正在實行或許實行終了」的操縱單位。這個觀點不是那末好明白,假如要說得簡樸一點就是:之前的 VDOM 樹節點的升級版。讓我們引見幾個癥結屬性:

  • 由「 遞歸改輪迴 」我們可以得知,當我們輪迴的遍歷樹抵達底部時,需要回到其父節點,那末對應的就是 Fiber 中的 return 屬性(之前叫 parent )。 childsibling 相似,代表這個 Fiber 的子 Fiber 和兄弟 Fiber
  • stateNode 這個屬性比較特別,用於紀錄當前 Fiber 所對應的實在 DOM 節點 或許 當前假造組件的實例,這麼做的緣由第一是為了完成 Ref ,第二是為了完成 DOM 的跟蹤
  • tag 屬性在新版的 React 中一共有 14 種值,離別代表了差別的 JSX 範例。
  • effectTageffects 這兩個屬性為的是紀錄每一個節點 Diff 后需要變動的狀況,比方刪除,挪動,插進去,替代,更新等…

alternate 屬性我想拿出來零丁說一下,這個屬性是 Fiber 架構新到場的屬性。我們都曉得,VDOM 算法是在更新的時刻天生一顆新的 VDOM 樹,去和舊的舉行對照。在 Fiber 架構中,當我們挪用 ReactDOM.render 或許 setState 今後,會天生一顆樹叫做:work-in-progress tree,這一顆樹就是我們所謂的新樹用來與我們的舊樹舉行對照,新的樹和舊的樹的 Fiber 是完整不一樣的,此時,我們就需要 alternate 屬性去鏈接新樹和舊樹。

司徒正美的研討中,一個 Fiber 和它的 alternate 屬性構成了一個聯嬰體,他們有配合的 tagtypestateNode 屬性,這些屬性在毛病邊境自爆時,用於恢復當前節點。

最先寫代碼:Component 組織函數

講了那末多的理論,人人肯定是暈了,然則沒辦法,Fiber 架構已比之前的簡樸 React 要龐雜太多了,因而不可能希望一次性把 Fiber 的內容悉數明白,需要重複多看。

固然,連繫代碼來梳理,思緒舊越發清楚了。我們在構建新的架構時,老的 Luy 代碼大部份都要舉行重構了,先來看看幾個主要重構的處所:

export class Component {
  constructor(props, context) {
    this.props = props
    this.context = context
    this.state = this.state || {}
    this.refs = {}
    this.updater = {}
  }

  setState(updater) {
    scheduleWork(this, updater)
  }

  render() {
    throw 'should implement `render()` function'
  }
}

Component.prototype.isReactComponent = true
  • 這就是 React.Component 的代碼
  • 組織函數中,我們都進兩個參數,一個是外部的 props ,一個是 context
  • 內部有 staterefsupdaterupdater 用於網絡 setState 的信息,便於今后更新用。固然,在這個版本當中,我並沒有運用。
  • setState 函數也並沒有做行列處置懲罰,只是挪用了 scheduleWork 這個函數
  • Component.prototype.isReactComponent = true ,這段代碼錶飾着,假如一個組件的範例為 function 且具有 isReactComponent ,那末他就是一個有狀況組件,在建立實例時需要用 new ,而無狀況組件只需要 fn(props,context) 挪用
const tag = {
  HostComponent: 'host',
  ClassComponent: 'class',
  HostRoot: 'root',
  HostText: 6,
  FunctionalComponent: 1
}

const updateQueue = []

export function render(Vnode, Container, callback) {
  updateQueue.push({
    fromTag: tag.HostRoot,
    stateNode: Container,
    props: { children: Vnode }
  })

  requestIdleCallback(performWork) //最先幹活
}

export function scheduleWork(instance, partialState) {
  updateQueue.push({
    fromTag: tag.ClassComponent,
    stateNode: instance,
    partialState: partialState
  })
  requestIdleCallback(performWork) //最先幹活
}

我們定義了一個全局變量 updateQueue 來紀錄我們一切的更新操縱,每當 renderscheduleWork (setState) 觸發時,我們都邑往 updateQueuepush 一個狀況,然後,進而挪用赫赫有名的 requestIdleCallback 舉行更新。在這裏與之前的 react 15 最大差別是,更新階段和初次襯着階段得到了一致,都是運用了 updateQueue 舉行更新。

實際上這裏另有優化的空間,就是屢次 setState 的時刻,應當合併成一次再舉行 requestIdleCallback 的挪用,不過這並非我們的目的,我們的目的是搞懂 Fiber 架構。requestIdleCallback 挪用的是 performWork 函數,我們接下來看看

performWork 函數

const EXPIRATION_TIME = 1 // ms async 過期時刻
let nextUnitOfWork = null
let pendingCommit = null

function performWork(deadline) {
  workLoop(deadline)
  if (nextUnitOfWork || updateQueue.length > 0) {
    requestIdleCallback(performWork) //繼承干
  }
}

function workLoop(deadline) {
  if (!nextUnitOfWork) {
    //一個周期內只建立一次
    nextUnitOfWork = createWorkInProgress(updateQueue)
  }

  while (nextUnitOfWork && deadline.timeRemaining() > EXPIRATION_TIME) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
  }

  if (pendingCommit) {
    //當全局 pendingCommit 變量被負值
    commitAllwork(pendingCommit)
  }
}

熟習 requestIdleCallback 的同硯肯定對這兩個函數並不生疏,這兩個函數實在做的就是所謂的異步調理。

《「React 16」為 Luy 完成 React Fiber 架構》

performWork 函數主要做了兩件事,第一件事就是拿到 deadline 進入我們之前所謂的大輪迴,也就是正式進入處置懲罰新舊 FiberDiff 階段,這個階段比較的巧妙,我們叫他 workLoop 階段。workLoop 會一次處置懲罰 1 個或許多個 Fiber ,詳細處置懲罰若干個,要看每一幀詳細還剩下若干時刻,假如一個 Fiber 斲喪太多時刻,那末就會比及下一幀再處置懲罰下一個 Fiber ,云云輪迴,遍歷全部 VDOM 樹。

在這裏我們注重到,假如一個
Fiber 斲喪太多時刻,可能會致使一幀時刻的過期,不過實在沒什麼題目啦,也僅僅是一幀過期罷了,關於我們視覺上並沒有多大的影響。

workLoop 函數重如果三部曲:

  1. createWorkInProgress 這個函數會構建一顆樹的頂端,賦值給全局變量 nextUnitOfWork ,經由歷程迭代的體式格局,不停更新 nextUnitOfWork 直到遍歷完一切樹的節點。
  2. performUnitOfWork 函數是第二步,不停的檢測當前幀是不是還剩餘時刻,舉行 WorkInProgress tree 的迭代
  3. WorkInProgress tree 迭代終了今後,挪用 commitAllWork ,將一切的變動悉數一次性的更新到 DOM 中,以保證 UI 的一連性

一切的 Diff 和建立實在 DOM 的操縱,都在 performUnitOfWork 當中,然則插進去和刪除是在 commitAllWork 當中。接下來,我們一一剖析三部曲的內部操縱。

第一步:createWorkInProgress

export function createWorkInProgress(updateQueue) {
  const updateTask = updateQueue.shift()
  if (!updateTask) return

  if (updateTask.partialState) {
    // 證實這是一個setState操縱
    updateTask.stateNode._internalfiber.partialState = updateTask.partialState
  }

  const rootFiber =
    updateTask.fromTag === tag.HostRoot
      ? updateTask.stateNode._rootContainerFiber
      : getRoot(updateTask.stateNode._internalfiber)

  return {
    tag: tag.HostRoot,
    stateNode: updateTask.stateNode,
    props: updateTask.props || rootFiber.props,
    alternate: rootFiber // 用於鏈接新舊的 VDOM
  }
}

function getRoot(fiber) {
  let _fiber = fiber
  while (_fiber.return) {
    _fiber = _fiber.return
  }
  return _fiber

這個函數的主要作用就是構建 workInProgress 樹的頂端並賦值給全局變量 nextUnitOfWork。

起首,我們先從 updateQueue 中獵取一個使命對象 updateTask 。隨後,舉行推斷是不是是更新階段。然後獵取 workInProgress 樹的頂端。假如是第一次襯着, RootFiber 的值是空的,由於我們並沒有構建任何的樹。

末了,我們將返回一個 Fiber 對象,這個 Fiber 對象的標識符( tag )是 HostRoot

第二步:performUnitOfWork

// 最先遍歷
function performUnitOfWork(workInProgress) {
  const nextChild = beginWork(workInProgress)
  if (nextChild) return nextChild

  // 沒有 nextChild, 我們看看這個節點有無 sibling
  let current = workInProgress
  while (current) {
    //網絡當前節點的effect,然後向上通報
    completeWork(current)
    if (current.sibling) return current.sibling
    //沒有 sibling,回到這個節點的父親,看看有無sibling
    current = current.return
  }
}

我們挪用 performUnitOfWork 處置懲罰我們的 workInProgress

全部函數做的事變實在就是一個左遍歷樹的歷程。起首,我們挪用 beginWork ,取得一個當前 Fiber 下的第一個孩子,假如有直接返回出去給 nextUnitOfWork ,看成下一個處置懲罰的節點;假如沒有找到任何孩子,證實我們已抵達了樹的底部,經由歷程下面的 while 輪迴,回到當前節點的父節點,將當前 Fiber 下具有 Effect 的孩子悉數紀錄下來,以便於今后更新 DOM

然後查找當前節點的父親節點,是不是有兄弟,有就返回,當做下一個處置懲罰的節點,假如沒有,就繼承回溯。

全部歷程用圖來示意,就是:

《「React 16」為 Luy 完成 React Fiber 架構》

在議論第三部之前,我們依然有兩個疑惑的處所:

  1. beginWork 是怎樣建立孩子的
  2. completeWork 是怎樣網絡 effect 的接下來,我們就來一同看看

beginWork

function beginWork(currentFiber) {
  switch (currentFiber.tag) {
    case tag.ClassComponent: {
      return updateClassComponent(currentFiber)
    }
    case tag.FunctionalComponent: {
      return updateFunctionalComponent(currentFiber)
    }
    default: {
      return updateHostComponent(currentFiber)
    }
  }
}

function updateHostComponent(currentFiber) {
  // 當一個 fiber 對應的 stateNode 是原生節點,那末他的 children 就放在 props 里
  if (!currentFiber.stateNode) {
    if (currentFiber.type === null) {
      //代表這是筆墨節點
      currentFiber.stateNode = document.createTextNode(currentFiber.props)
    } else {
      //代表這是實在原生 DOM 節點
      currentFiber.stateNode = document.createElement(currentFiber.type)
    }
  }
  const newChildren = currentFiber.props.children
  return reconcileChildrenArray(currentFiber, newChildren)
}

function updateFunctionalComponent(currentFiber) {
  let type = currentFiber.type
  let props = currentFiber.props
  const newChildren = currentFiber.type(props)

  return reconcileChildrenArray(currentFiber, newChildren)
}

function updateClassComponent(currentFiber) {
  let instance = currentFiber.stateNode
  if (!instance) {
    // 假如是 mount 階段,構建一個 instance
    instance = currentFiber.stateNode = createInstance(currentFiber)
  }

  // 將新的state,props刷給當前的instance
  instance.props = currentFiber.props
  instance.state = { ...instance.state, ...currentFiber.partialState }

  // 清空 partialState
  currentFiber.partialState = null
  const newChildren = currentFiber.stateNode.render()

  // currentFiber 代表老的,newChildren代表新的
  // 這個函數會返回孩子行列的第一個
  return reconcileChildrenArray(currentFiber, newChildren)
}

beginWork 實際上是一個推斷分支的函數,全部函數的意義是:

  • 推斷當前的 Fiber 是什麼範例,是 class 的走 class 分支,是 stateless 的走 stateless,是原生節點的走原生分支
  • 假如沒有 stateNode ,則建立一個 stateNode
  • 假如是 class ,則建立實例,挪用 render 函數,襯着其兒子;假如是原生節點,挪用 DOM API 建立原生節點;假如是 stateless ,就實行它,襯着出 VDOM 節點
  • 末了,走到最主要的函數, recocileChildrenArray 函數,將其每一個孩子舉行鏈表的鏈接,舉行 diff ,然後返回當前 Fiber 之下的第一個孩子

我們來看看比較主要的 classComponent 的構建流程

function updateClassComponent(currentFiber) {
  let instance = currentFiber.stateNode
  if (!instance) {
    // 假如是 mount 階段,構建一個 instance
    instance = currentFiber.stateNode = createInstance(currentFiber)
  }

  // 將新的state,props刷給當前的instance
  instance.props = currentFiber.props
  instance.state = { ...instance.state, ...currentFiber.partialState }

  // 清空 partialState
  currentFiber.partialState = null
  const newChildren = currentFiber.stateNode.render()

  // currentFiber 代表老的,newChildren代表新的
  // 這個函數會返回孩子行列的第一個
  return reconcileChildrenArray(currentFiber, newChildren)
}

function createInstance(fiber) {
  const instance = new fiber.type(fiber.props)
  instance._internalfiber = fiber
  return instance
}

假如是初次襯着,那末組件並沒有被實例話,此時我們挪用 createInstance 實例化組件,然後將當前的 propsstate 賦值給 props 、state ,隨後我們挪用 render 函數,取得了新兒子 newChildren

襯着出新兒子今後,來到了新架構下最主要的中心函數 reconcileChildrenArray .

reconcileChildrenArray

const PLACEMENT = 1
const DELETION = 2
const UPDATE = 3

function placeChild(currentFiber, newChild) {
  const type = newChild.type

  if (typeof newChild === 'string' || typeof newChild === 'number') {
    // 假如這個節點沒有 type ,這個節點就多是 number 或許 string
    return createFiber(tag.HostText, null, newChild, currentFiber, PLACEMENT)
  }

  if (typeof type === 'string') {
    // 原生節點
    return createFiber(tag.HOST_COMPONENT, newChild.type, newChild.props, currentFiber, PLACEMENT)
  }

  if (typeof type === 'function') {
    const _tag = type.prototype.isReactComponent ? tag.CLASS_COMPONENT : tag.FunctionalComponent

    return {
      type: newChild.type,
      tag: _tag,
      props: newChild.props,
      return: currentFiber,
      effectTag: PLACEMENT
    }
  }
}

function reconcileChildrenArray(currentFiber, newChildren) {
  // 對照節點,雷同的標記更新
  // 差別的標記 替代
  // 過剩的標記刪除,而且紀錄下來
  const arrayfiyChildren = arrayfiy(newChildren)

  let index = 0
  let oldFiber = currentFiber.alternate ? currentFiber.alternate.child : null
  let newFiber = null

  while (index < arrayfiyChildren.length || oldFiber !== null) {
    const prevFiber = newFiber
    const newChild = arrayfiyChildren[index]
    const isSameFiber = oldFiber && newChild && newChild.type === oldFiber.type

    if (isSameFiber) {
      newFiber = {
        type: oldFiber.type,
        tag: oldFiber.tag,
        stateNode: oldFiber.stateNode,
        props: newChild.props,
        return: currentFiber,
        alternate: oldFiber,
        partialState: oldFiber.partialState,
        effectTag: UPDATE
      }
    }

    if (!isSameFiber && newChild) {
      newFiber = placeChild(currentFiber, newChild)
    }

    if (!isSameFiber && oldFiber) {
      // 這個狀況的意義是新的節點比舊的節點少
      // 這時刻,我們要將變動的 effect 放在本節點的 list 里
      oldFiber.effectTag = DELETION
      currentFiber.effects = currentFiber.effects || []
      currentFiber.effects.push(oldFiber)
    }

    if (oldFiber) {
      oldFiber = oldFiber.sibling || null
    }

    if (index === 0) {
      currentFiber.child = newFiber
    } else if (prevFiber && newChild) {
      // 這裏不懂是幹嗎的
      prevFiber.sibling = newFiber
    }

    index++
  }
  return currentFiber.child
}

這個函數做了幾件事

  • 將孩子 array 化,這麼做可以使得 reactrender 函數返回數組
  • currentFiber 是新的 workInProgress 上的一個節點,是屬於新的 VDOM 樹 ,而此時,我們必需要找到舊的 VDOM 樹來舉行比對。那末在這裏, Alternate 屬性就起到了癥結性作用,這個屬性鏈接了舊的 VDOM ,使得我們可以獵取本來的 VDOM
  • 接下來我們舉行對照,假如新的節點的 type 與本來的雷同,那末我們將新建一個 Fiber ,標記這個 FiberUPDATE
  • 假如新的節點的 type 與本來的不雷同,那我們運用 PALCEMENT 來標記他
  • 假如舊的節點數目比新的節點少,那就證實,我們要刪除舊的節點,我們把舊節點標記為 DELETION ,並構建一個 effect list 紀錄下來
  • 當前遍歷的是組件的第一個孩子,那末我們將他紀錄在 currentFiberchild 字段中
  • 當遍歷的不是第一個孩子,我們將 新建的 newFiber 用鏈表的情勢將他們一同推入到 currentFiber
  • 返回當前 currentFiber 下的第一個孩子

看着比較煩瑣,然則實際上做的就是構建鏈表和 diff 孩子的歷程,這個函數有許多優化的空間,運用 key 今後,在這裡能進步許多的機能,為了簡樸,我並沒有對 key 舉行操縱,今後的 Luy 版本肯定會的。

completeWork: 網絡 effectTag

// 最先遍歷
function performUnitOfWork(workInProgress) {
  const nextChild = beginWork(workInProgress)
  if (nextChild) return nextChild

  // 沒有 nextChild, 我們看看這個節點有無 sibling
  let current = workInProgress
  while (current) {
    //網絡當前節點的effect,然後向上通報
    completeWork(current)
    if (current.sibling) return current.sibling
    //沒有 sibling,回到這個節點的父親,看看有無sibling
    current = current.return
  }
}

//網絡有 effecttag 的 fiber
function completeWork(currentFiber) {
  if (currentFiber.tag === tag.classComponent) {
    // 用於回溯最高點的 root
    currentFiber.stateNode._internalfiber = currentFiber
  }

  if (currentFiber.return) {
    const currentEffect = currentFiber.effects || [] //網絡當前節點的 effect list
    const currentEffectTag = currentFiber.effectTag ? [currentFiber] : []
    const parentEffects = currentFiber.return.effects || []
    currentFiber.return.effects = parentEffects.concat(currentEffect, currentEffectTag)
  } else {
    // 抵達最頂端了
    pendingCommit = currentFiber
  }
}

這個函數做了兩件事,第一件事變就是網絡當前 currentFibereffectTag ,將其 append 到父 Fibereffectlist 中去,經由歷程輪迴一層一層往上,終究抵達頂端 currentFiber.return === void 666 的時刻,證實我們抵達了 root ,此時我們已把一切的 effect 網絡到了頂端的 currentFiber.effect 上,並把它賦值給 pendingCommit ,進入 commitAllWork 階段。

第三步:commitAllWork

終究,我們已經由歷程不停不停的挪用 requestIdleCallback 和 大輪迴,將我們的一切變動都找出來放在了 workInProgress tree 里,我們接下來就要做末了一步:將一切的變動一次性的變動到實在 DOM 中,注重,這個階段里我們不再運轉建立 DOMrender ,因而,雖然我們一次性變動一切的 DOM ,然則機能來講並非太差。

function commitAllwork(topFiber) {
  topFiber.effects.forEach(f => {
    commitWork(f)
  })

  topFiber.stateNode._rootContainerFiber = topFiber
  topFiber.effects = []
  nextUnitOfWork = null
  pendingCommit = null
}

我們直接拿到 TopFiber 中的 effects list ,遍歷,將變動悉數打到 DOM 中去,然後我們將全局變量清算清潔。

function commitWork(effectFiber) {
  if (effectFiber.tag === tag.HostRoot) {
    // 代表 root 節點沒什麼必要操縱
    return
  }

  // 拿到parent的緣由是,我們要將元素插進去的點,插在父親的下面
  let domParentFiber = effectFiber.return
  while (domParentFiber.tag === tag.classComponent || domParentFiber.tag === tag.FunctionalComponent) {
    // 假如是 class 就直接跳過,由於 class 範例的fiber.stateNode 是其自身實例
    domParentFiber = domParentFiber.return
  }

  //拿到父親的實在 DOM
  const domParent = domParentFiber.stateNode
  if (effectFiber.effectTag === PLACEMENT) {
    if (effectFiber.tag === tag.HostComponent || effectFiber.tag === tag.HostText) {
      //經由歷程 tag 搜檢是不是是實在的節點
      domParent.appendChild(effectFiber.stateNode)
    }
    // 其他狀況
  } else if (effectFiber.effectTag == UPDATE) {
    // 更新邏輯 只能是沒完成
  } else if (effectFiber.effectTag == DELETION) {
    //刪除過剩的舊節點
    commitDeletion(effectFiber, domParent)
  }
}

function commitDeletion(fiber, domParent) {
  let node = fiber
  while (true) {
    if (node.tag == tag.classComponent) {
      node = node.child
      continue
    }
    domParent.removeChild(node.stateNode)
    while (node != fiber && !node.sibling) {
      node = node.return
    }
    if (node == fiber) {
      return
    }
    node = node.sibling
  }
}

這一部份代碼是最好明白的了,就是做的是刪除和插進去或許更新 DOM 的操縱,值得注重的是,刪除操縱照舊運用的鏈表操縱。

末了來一段測試代碼:

import React from './Luy/index'
import { Component } from './component'
import { render } from './vdom'

class App extends Component {
  state = {
    info: true
  }
  constructor(props) {
    super(props)

    setTimeout(() => {
      this.setState({
        info: !this.state.info
      })
    }, 1000)
  }

  render() {
    return (
      <div>
        <span>hello</span>
        <span>luy</span>
        <div>{this.state.info ? 'imasync' : 'iminfo'}</div>
      </div>
    )
  }
}
render(<App />, document.getElementById('root'))

我們來看看動圖吧!當節點 mount 今後,過了 1 秒,就會更新,我們簡樸的更新就到此結束了

《「React 16」為 Luy 完成 React Fiber 架構》
《「React 16」為 Luy 完成 React Fiber 架構》

再看以下挪用棧,我們的 requestIdleCallback 函數已準確的運轉了。

假如你想下載代碼親身體驗,可以到 Luy 堆棧中:

git clone https://github.com/Foveluy/Luy.git
cd Luy
npm i --save-dev
npm run start

如今我能找到的一切材料都放在堆棧中:材料

回憶本文幾個主要的點

一最先我們就運用了一個數組來紀錄 update 的信息,經由歷程挪用 requestIdleCallback 來將更新一個一個的取出來,大部份時刻行列里只要一個。

取出來今後,運用從左向右遍歷的體式格局,用鏈錶鏈接一個一個的 Fiber ,並做 diff 和建立,末了一次性的 patch 到實在 DOM 中去。

如今 react 的架構已變得極為龐雜,而本文也只是將 React 的團體架構通篇流程形貌了一遍,內里的細節照舊值得我們的窮究,比方,怎樣通報 context ,怎樣完成 ref ,怎樣完成毛病邊境處置懲罰,聲明周期的處置懲罰,這些都是很大的話題,在接下去的文章里,我會一步一步的將這些關聯講清楚。

末了,感謝支撐我的迷你框架項目:Luy ,如今正在向 Fiber 升級!假如你喜好,請給我一點 star🌟 示意勉勵!感謝

假如有什麼題目,可以到場我們的進修 QQ 群: 370262116 ,群里險些一切的迷你 React 作者都在了,包含 anu 作者司徒正美, omi 作者,我等,一同來進修吧!

    原文作者:215566435
    原文地址: https://segmentfault.com/a/1190000014957987
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞