成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

「React 16」為 Luy 實(shí)現(xiàn) React Fiber 架構(gòu)

DevWiki / 2418人閱讀

摘要:開(kāi)始寫(xiě)代碼構(gòu)造函數(shù)講了那么多的理論,大家一定是暈了,但是沒(méi)辦法,架構(gòu)已經(jīng)比之前的簡(jiǎn)單要復(fù)雜太多了,因此不可能指望一次性把的內(nèi)容全部理解,需要反復(fù)多看。

前言

Facebook 的研發(fā)能力真是驚人, Fiber 架構(gòu)給 React 帶來(lái)了新視野的同時(shí),將調(diào)度一詞介紹給了前端,然而這個(gè)架構(gòu)實(shí)在不好懂,比起以前的 Vdom 樹(shù),新的 Fiber 樹(shù)就麻煩太多。

可以說(shuō),React 16 和 React 15 已經(jīng)是技巧上的分水嶺,但是得益于 React 16 的 Fiber 架構(gòu),使得 React 即使在沒(méi)有開(kāi)啟異步的情況下,性能依舊是得到了提高。

經(jīng)過(guò)兩個(gè)星期的痛苦研究,終于將 React 16 的渲染脈絡(luò)摸得比較清晰,可以寫(xiě)文章來(lái)記錄、回顧一下。

如果你已經(jīng)稍微理解了 Fiber 架構(gòu),可以直接看代碼:倉(cāng)庫(kù)地址

什么是 React Fiber ?

React Fiber 并不是所謂的纖程(微線(xiàn)程、協(xié)程),而是一種基于瀏覽器的單線(xiàn)程調(diào)度算法,背后的支持 API 是大名鼎鼎的: requestIdleCallback ,得到了這個(gè) API 的支持,我們便可以將 React 中最耗時(shí)的部分放入其中。

回顧 React 歷年來(lái)的算法都知道,reconcilation 算法實(shí)際上是一個(gè)大遞歸,大遞歸一旦進(jìn)行,想要中斷還是比較不好操作的,加上頭大尾大的 React 15 代碼已經(jīng)膨脹到了不可思議的地步,在重重壓力之下,React 使用了大循環(huán)來(lái)代替之前的大遞歸,雖然代碼變得比遞歸難懂了幾個(gè)梯度,但是實(shí)際上,代碼量比原來(lái)少了非常多(開(kāi)發(fā)版本 3W 行壓縮到了 1.3W 行)

那問(wèn)題就來(lái)了,什么是 Fiber :一種將 recocilation (遞歸 diff ),拆分成無(wú)數(shù)個(gè)小任務(wù)的算法;它隨時(shí)能夠停止,恢復(fù)。停止恢復(fù)的時(shí)機(jī)取決于當(dāng)前的一幀( 16ms )內(nèi),還有沒(méi)有足夠的時(shí)間允許計(jì)算。

React 異步渲染流程圖

用戶(hù)調(diào)用 ReactDOM.render 方法,傳入例如組件,React 開(kāi)始運(yùn)作

在內(nèi)部會(huì)被轉(zhuǎn)換成 RootFiber 節(jié)點(diǎn),一個(gè)特殊的節(jié)點(diǎn),并記錄在一個(gè)全局變量中,TopTree

拿到 RootFiber ,首先創(chuàng)建一個(gè) 對(duì)應(yīng)的 Fiber ,然后加上 Fiber 信息,以便之后回溯。隨后,賦值給之前的全局變量 TopTree

使用 requestIdleCallback 重復(fù)第三個(gè)步驟,直到循環(huán)到樹(shù)的所有節(jié)點(diǎn)

最后完成了 diff 階段,一次性將變化更新到真實(shí) DOM 中,以防止 UI 展示的不連續(xù)性

其中,重點(diǎn)就是 34 階段,這兩個(gè)階段將創(chuàng)建真實(shí) DOM 和組件渲染 ( render )拆分為無(wú)數(shù)的小碎塊,使用 requestIdleCallback 連續(xù)進(jìn)行。在 React 15 的時(shí)候,渲染、創(chuàng)建、插入、刪除等操作是最費(fèi)時(shí)的,在 React 16 中將渲染、創(chuàng)建抽離出來(lái)分片,這樣性能就得到了極大的提升。

那為什么更新到真實(shí) DOM 中不能拆分呢?理論上來(lái)說(shuō),是可以拆分的,但是這會(huì)造成 UI 的不連續(xù)性,極大的影響體驗(yàn)。

遞歸變成了循環(huán)

以簡(jiǎn)單的組件為例子:

從頂端的 div#root 向下走,先走左子樹(shù)

div 有兩個(gè)孩子 span ,繼續(xù)走左邊的

來(lái)到 span ,之下只有一個(gè) hello ,到此,不再繼續(xù)往下,而是往上回到 span

因?yàn)?span 有一個(gè)兄弟,因此往兄弟 span 走去

兄弟 span 有孩子 luy ,到此,不繼續(xù)往下,而是回到 luy 的老爹 span

luy 的老爹 span 右邊沒(méi)有兄弟了,因此回到其老爹 div

div 沒(méi)有任何的兄弟,因此回到頂端的 div#root

每經(jīng)過(guò)一個(gè) Fiber 節(jié)點(diǎn),執(zhí)行 render 或者 document.createElement (或者更新 DOM )的操作

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

一個(gè) Fiber 數(shù)據(jù)結(jié)構(gòu)比較復(fù)雜

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: []
}

這是一個(gè)比較完整的 Fiber object,他復(fù)雜的原因是因?yàn)橐粋€(gè) Fiber 就代表了一個(gè)「正在執(zhí)行或者執(zhí)行完畢」的操作單元。這個(gè)概念不是那么好理解,如果要說(shuō)得簡(jiǎn)單一點(diǎn)就是:以前的 VDOM 樹(shù)節(jié)點(diǎn)的升級(jí)版。讓我們介紹幾個(gè)關(guān)鍵屬性:

由「 遞歸改循環(huán) 」我們可以得知,當(dāng)我們循環(huán)的遍歷樹(shù)到達(dá)底部時(shí),需要回到其父節(jié)點(diǎn),那么對(duì)應(yīng)的就是 Fiber 中的 return 屬性(以前叫 parent )。 childsibling 類(lèi)似,代表這個(gè) Fiber 的子 Fiber 和兄弟 Fiber

stateNode 這個(gè)屬性比較特殊,用于記錄當(dāng)前 Fiber 所對(duì)應(yīng)的真實(shí) DOM 節(jié)點(diǎn) 或者 當(dāng)前虛擬組件的實(shí)例,這么做的原因第一是為了實(shí)現(xiàn) Ref ,第二是為了實(shí)現(xiàn) DOM 的跟蹤

tag 屬性在新版的 React 中一共有 14 種值,分別代表了不同的 JSX 類(lèi)型。

effectTageffects 這兩個(gè)屬性為的是記錄每個(gè)節(jié)點(diǎn) Diff 后需要變更的狀態(tài),比如刪除,移動(dòng),插入,替換,更新等...

alternate 屬性我想拿出來(lái)多帶帶說(shuō)一下,這個(gè)屬性是 Fiber 架構(gòu)新加入的屬性。我們都知道,VDOM 算法是在更新的時(shí)候生成一顆新的 VDOM 樹(shù),去和舊的進(jìn)行對(duì)比。在 Fiber 架構(gòu)中,當(dāng)我們調(diào)用 ReactDOM.render 或者 setState 之后,會(huì)生成一顆樹(shù)叫做:work-in-progress tree,這一顆樹(shù)就是我們所謂的新樹(shù)用來(lái)與我們的舊樹(shù)進(jìn)行對(duì)比,新的樹(shù)和舊的樹(shù)的 Fiber 是完全不一樣的,此時(shí),我們就需要 alternate 屬性去鏈接新樹(shù)和舊樹(shù)。

司徒正美的研究中,一個(gè) Fiber 和它的 alternate 屬性構(gòu)成了一個(gè)聯(lián)嬰體,他們有共同的 tagtype,stateNode 屬性,這些屬性在錯(cuò)誤邊界自爆時(shí),用于恢復(fù)當(dāng)前節(jié)點(diǎn)。

開(kāi)始寫(xiě)代碼:Component 構(gòu)造函數(shù)

講了那么多的理論,大家一定是暈了,但是沒(méi)辦法,Fiber 架構(gòu)已經(jīng)比之前的簡(jiǎn)單 React 要復(fù)雜太多了,因此不可能指望一次性把 Fiber 的內(nèi)容全部理解,需要反復(fù)多看。

當(dāng)然,結(jié)合代碼來(lái)梳理,思路舊更加清晰了。我們?cè)跇?gòu)建新的架構(gòu)時(shí),老的 Luy 代碼大部分都要進(jìn)行重構(gòu)了,先來(lái)看看幾個(gè)主要重構(gòu)的地方:

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 的代碼

構(gòu)造函數(shù)中,我們都進(jìn)兩個(gè)參數(shù),一個(gè)是外部的 props ,一個(gè)是 context

內(nèi)部有 state,refs,updater, updater 用于收集 setState 的信息,便于之后更新用。當(dāng)然,在這個(gè)版本之中,我并沒(méi)有使用。

setState 函數(shù)也并沒(méi)有做隊(duì)列處理,只是調(diào)用了 scheduleWork 這個(gè)函數(shù)

Component.prototype.isReactComponent = true ,這段代碼表飾著,如果一個(gè)組件的類(lèi)型為 function 且擁有 isReactComponent ,那么他就是一個(gè)有狀態(tài)組件,在創(chuàng)建實(shí)例時(shí)需要用 new ,而無(wú)狀態(tài)組件只需要 fn(props,context) 調(diào)用

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) //開(kāi)始干活
}

export function scheduleWork(instance, partialState) {
  updateQueue.push({
    fromTag: tag.ClassComponent,
    stateNode: instance,
    partialState: partialState
  })
  requestIdleCallback(performWork) //開(kāi)始干活
}

我們定義了一個(gè)全局變量 updateQueue 來(lái)記錄我們所有的更新操作,每當(dāng) renderscheduleWork (setState) 觸發(fā)時(shí),我們都會(huì)往 updateQueuepush 一個(gè)狀態(tài),然后,進(jìn)而調(diào)用大名鼎鼎的 requestIdleCallback 進(jìn)行更新。在這里與之前的 react 15 最大不同是,更新階段和首次渲染階段得到了統(tǒng)一,都是使用了 updateQueue 進(jìn)行更新。

實(shí)際上這里還有優(yōu)化的空間,就是多次 setState 的時(shí)候,應(yīng)該合并成一次再進(jìn)行 requestIdleCallback 的調(diào)用,不過(guò)這并不是我們的目標(biāo),我們的目標(biāo)是搞懂 Fiber 架構(gòu)。requestIdleCallback 調(diào)用的是 performWork 函數(shù),我們接下來(lái)看看

performWork 函數(shù)
const EXPIRATION_TIME = 1 // ms async 逾期時(shí)間
let nextUnitOfWork = null
let pendingCommit = null

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

function workLoop(deadline) {
  if (!nextUnitOfWork) {
    //一個(gè)周期內(nèi)只創(chuàng)建一次
    nextUnitOfWork = createWorkInProgress(updateQueue)
  }

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

  if (pendingCommit) {
    //當(dāng)全局 pendingCommit 變量被負(fù)值
    commitAllwork(pendingCommit)
  }
}

熟悉 requestIdleCallback 的同學(xué)一定對(duì)這兩個(gè)函數(shù)并不陌生,這兩個(gè)函數(shù)其實(shí)做的就是所謂的異步調(diào)度。

performWork 函數(shù)主要做了兩件事,第一件事就是拿到 deadline 進(jìn)入我們之前所謂的大循環(huán),也就是正式進(jìn)入處理新舊 FiberDiff 階段,這個(gè)階段比較的奇妙,我們叫他 workLoop 階段。workLoop 會(huì)一次處理 1 個(gè)或者多個(gè) Fiber ,具體處理多少個(gè),要看每一幀具體還剩下多少時(shí)間,如果一個(gè) Fiber 消耗太多時(shí)間,那么就會(huì)等到下一幀再處理下一個(gè) Fiber ,如此循環(huán),遍歷整個(gè) VDOM 樹(shù)。

在這里我們注意到,如果一個(gè) Fiber 消耗太多時(shí)間,可能會(huì)導(dǎo)致一幀時(shí)間的逾期,不過(guò)其實(shí)沒(méi)什么問(wèn)題啦,也僅僅是一幀逾期而已,對(duì)于我們視覺(jué)上并沒(méi)有多大的影響。

workLoop 函數(shù)主要是三部曲:

createWorkInProgress 這個(gè)函數(shù)會(huì)構(gòu)建一顆樹(shù)的頂端,賦值給全局變量 nextUnitOfWork ,通過(guò)迭代的方式,不斷更新 nextUnitOfWork 直到遍歷完所有樹(shù)的節(jié)點(diǎn)。

performUnitOfWork 函數(shù)是第二步,不斷的檢測(cè)當(dāng)前幀是否還剩余時(shí)間,進(jìn)行 WorkInProgress tree 的迭代

當(dāng) WorkInProgress tree 迭代完畢以后,調(diào)用 commitAllWork ,將所有的變更全部一次性的更新到 DOM 中,以保證 UI 的連續(xù)性

所有的 Diff 和創(chuàng)建真實(shí) DOM 的操作,都在 performUnitOfWork 之中,但是插入和刪除是在 commitAllWork 之中。接下來(lái),我們逐一分析三部曲的內(nèi)部操作。

第一步:createWorkInProgress
export function createWorkInProgress(updateQueue) {
  const updateTask = updateQueue.shift()
  if (!updateTask) return

  if (updateTask.partialState) {
    // 證明這是一個(gè)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

這個(gè)函數(shù)的主要作用就是構(gòu)建 workInProgress 樹(shù)的頂端并賦值給全局變量 nextUnitOfWork。

首先,我們先從 updateQueue 中獲取一個(gè)任務(wù)對(duì)象 updateTask 。隨后,進(jìn)行判斷是否是更新階段。然后獲取 workInProgress 樹(shù)的頂端。如果是第一次渲染, RootFiber 的值是空的,因?yàn)槲覀儾](méi)有構(gòu)建任何的樹(shù)。

最后,我們將返回一個(gè) Fiber 對(duì)象,這個(gè) Fiber 對(duì)象的標(biāo)識(shí)符( tag )是 HostRoot 。

第二步:performUnitOfWork
// 開(kāi)始遍歷
function performUnitOfWork(workInProgress) {
  const nextChild = beginWork(workInProgress)
  if (nextChild) return nextChild

  // 沒(méi)有 nextChild, 我們看看這個(gè)節(jié)點(diǎn)有沒(méi)有 sibling
  let current = workInProgress
  while (current) {
    //收集當(dāng)前節(jié)點(diǎn)的effect,然后向上傳遞
    completeWork(current)
    if (current.sibling) return current.sibling
    //沒(méi)有 sibling,回到這個(gè)節(jié)點(diǎn)的父親,看看有沒(méi)有sibling
    current = current.return
  }
}

我們調(diào)用 performUnitOfWork 處理我們的 workInProgress 。

整個(gè)函數(shù)做的事情其實(shí)就是一個(gè)左遍歷樹(shù)的過(guò)程。首先,我們調(diào)用 beginWork ,獲得一個(gè)當(dāng)前 Fiber 下的第一個(gè)孩子,如果有直接返回出去給 nextUnitOfWork ,當(dāng)作下一個(gè)處理的節(jié)點(diǎn);如果沒(méi)有找到任何孩子,證明我們已經(jīng)到達(dá)了樹(shù)的底部,通過(guò)下面的 while 循環(huán),回到當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn),將當(dāng)前 Fiber 下?lián)碛?Effect 的孩子全部記錄下來(lái),以便于之后更新 DOM

然后查找當(dāng)前節(jié)點(diǎn)的父親節(jié)點(diǎn),是否有兄弟,有就返回,當(dāng)成下一個(gè)處理的節(jié)點(diǎn),如果沒(méi)有,就繼續(xù)回溯。

整個(gè)過(guò)程用圖來(lái)表示,就是:

在討論第三部之前,我們?nèi)匀挥袃蓚€(gè)迷惑的地方:

beginWork 是如何創(chuàng)建孩子的

completeWork 是如何收集 effect 的接下來(lái),我們就來(lái)一起看看

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) {
  // 當(dāng)一個(gè) fiber 對(duì)應(yīng)的 stateNode 是原生節(jié)點(diǎn),那么他的 children 就放在 props 里
  if (!currentFiber.stateNode) {
    if (currentFiber.type === null) {
      //代表這是文字節(jié)點(diǎn)
      currentFiber.stateNode = document.createTextNode(currentFiber.props)
    } else {
      //代表這是真實(shí)原生 DOM 節(jié)點(diǎn)
      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 階段,構(gòu)建一個(gè) instance
    instance = currentFiber.stateNode = createInstance(currentFiber)
  }

  // 將新的state,props刷給當(dāng)前的instance
  instance.props = currentFiber.props
  instance.state = { ...instance.state, ...currentFiber.partialState }

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

  // currentFiber 代表老的,newChildren代表新的
  // 這個(gè)函數(shù)會(huì)返回孩子隊(duì)列的第一個(gè)
  return reconcileChildrenArray(currentFiber, newChildren)
}

beginWork 其實(shí)是一個(gè)判斷分支的函數(shù),整個(gè)函數(shù)的意思是:

判斷當(dāng)前的 Fiber 是什么類(lèi)型,是 class 的走 class 分支,是 stateless 的走 stateless,是原生節(jié)點(diǎn)的走原生分支

如果沒(méi)有 stateNode ,則創(chuàng)建一個(gè) stateNode

如果是 class ,則創(chuàng)建實(shí)例,調(diào)用 render 函數(shù),渲染其兒子;如果是原生節(jié)點(diǎn),調(diào)用 DOM API 創(chuàng)建原生節(jié)點(diǎn);如果是 stateless ,就執(zhí)行它,渲染出 VDOM 節(jié)點(diǎn)

最后,走到最重要的函數(shù), recocileChildrenArray 函數(shù),將其每一個(gè)孩子進(jìn)行鏈表的鏈接,進(jìn)行 diff ,然后返回當(dāng)前 Fiber 之下的第一個(gè)孩子

我們來(lái)看看比較重要的 classComponent 的構(gòu)建流程

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

  // 將新的state,props刷給當(dāng)前的instance
  instance.props = currentFiber.props
  instance.state = { ...instance.state, ...currentFiber.partialState }

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

  // currentFiber 代表老的,newChildren代表新的
  // 這個(gè)函數(shù)會(huì)返回孩子隊(duì)列的第一個(gè)
  return reconcileChildrenArray(currentFiber, newChildren)
}

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

如果是首次渲染,那么組件并沒(méi)有被實(shí)例話(huà),此時(shí)我們調(diào)用 createInstance 實(shí)例化組件,然后將當(dāng)前的 propsstate 賦值給 props 、state ,隨后我們調(diào)用 render 函數(shù),獲得了新兒子 newChildren

渲染出新兒子之后,來(lái)到了新架構(gòu)下最重要的核心函數(shù) 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") {
    // 如果這個(gè)節(jié)點(diǎn)沒(méi)有 type ,這個(gè)節(jié)點(diǎn)就可能是 number 或者 string
    return createFiber(tag.HostText, null, newChild, currentFiber, PLACEMENT)
  }

  if (typeof type === "string") {
    // 原生節(jié)點(diǎn)
    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) {
  // 對(duì)比節(jié)點(diǎn),相同的標(biāo)記更新
  // 不同的標(biāo)記 替換
  // 多余的標(biāo)記刪除,并且記錄下來(lái)
  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) {
      // 這個(gè)情況的意思是新的節(jié)點(diǎn)比舊的節(jié)點(diǎn)少
      // 這時(shí)候,我們要將變更的 effect 放在本節(jié)點(diǎn)的 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
}

這個(gè)函數(shù)做了幾件事

將孩子 array 化,這么做能夠使得 reactrender 函數(shù)返回?cái)?shù)組

currentFiber 是新的 workInProgress 上的一個(gè)節(jié)點(diǎn),是屬于新的 VDOM 樹(shù) ,而此時(shí),我們必須要找到舊的 VDOM 樹(shù)來(lái)進(jìn)行比對(duì)。那么在這里, Alternate 屬性就起到了關(guān)鍵性作用,這個(gè)屬性鏈接了舊的 VDOM ,使得我們能夠獲取原來(lái)的 VDOM

接下來(lái)我們進(jìn)行對(duì)比,如果新的節(jié)點(diǎn)的 type 與原來(lái)的相同,那么我們將新建一個(gè) Fiber ,標(biāo)記這個(gè) FiberUPDATE

如果新的節(jié)點(diǎn)的 type 與原來(lái)的不相同,那我們使用 PALCEMENT 來(lái)標(biāo)記他

如果舊的節(jié)點(diǎn)數(shù)量比新的節(jié)點(diǎn)少,那就證明,我們要?jiǎng)h除舊的節(jié)點(diǎn),我們把舊節(jié)點(diǎn)標(biāo)記為 DELETION ,并構(gòu)建一個(gè) effect list 記錄下來(lái)

當(dāng)前遍歷的是組件的第一個(gè)孩子,那么我們將他記錄在 currentFiberchild 字段中

當(dāng)遍歷的不是第一個(gè)孩子,我們將 新建的 newFiber 用鏈表的形式將他們一起推入到 currentFiber

返回當(dāng)前 currentFiber 下的第一個(gè)孩子

看著比較啰嗦,但是實(shí)際上做的就是構(gòu)建鏈表和 diff 孩子的過(guò)程,這個(gè)函數(shù)有很多優(yōu)化的空間,使用 key 以后,在這里能提高很多的性能,為了簡(jiǎn)單,我并沒(méi)有對(duì) key 進(jìn)行操作,之后的 Luy 版本一定會(huì)的。

completeWork: 收集 effectTag
// 開(kāi)始遍歷
function performUnitOfWork(workInProgress) {
  const nextChild = beginWork(workInProgress)
  if (nextChild) return nextChild

  // 沒(méi)有 nextChild, 我們看看這個(gè)節(jié)點(diǎn)有沒(méi)有 sibling
  let current = workInProgress
  while (current) {
    //收集當(dāng)前節(jié)點(diǎn)的effect,然后向上傳遞
    completeWork(current)
    if (current.sibling) return current.sibling
    //沒(méi)有 sibling,回到這個(gè)節(jié)點(diǎn)的父親,看看有沒(méi)有sibling
    current = current.return
  }
}

//收集有 effecttag 的 fiber
function completeWork(currentFiber) {
  if (currentFiber.tag === tag.classComponent) {
    // 用于回溯最高點(diǎn)的 root
    currentFiber.stateNode._internalfiber = currentFiber
  }

  if (currentFiber.return) {
    const currentEffect = currentFiber.effects || [] //收集當(dāng)前節(jié)點(diǎn)的 effect list
    const currentEffectTag = currentFiber.effectTag ? [currentFiber] : []
    const parentEffects = currentFiber.return.effects || []
    currentFiber.return.effects = parentEffects.concat(currentEffect, currentEffectTag)
  } else {
    // 到達(dá)最頂端了
    pendingCommit = currentFiber
  }
}

這個(gè)函數(shù)做了兩件事,第一件事情就是收集當(dāng)前 currentFibereffectTag ,將其 append 到父 Fibereffectlist 中去,通過(guò)循環(huán)一層一層往上,最終到達(dá)頂端 currentFiber.return === void 666 的時(shí)候,證明我們到達(dá)了 root ,此時(shí)我們已經(jīng)把所有的 effect 收集到了頂端的 currentFiber.effect 上,并把它賦值給 pendingCommit ,進(jìn)入 commitAllWork 階段。

第三步:commitAllWork

終于,我們已經(jīng)通過(guò)不斷不斷的調(diào)用 requestIdleCallback 和 大循環(huán),將我們的所有變更都找出來(lái)放在了 workInProgress tree 里,我們接下來(lái)就要做最后一步:將所有的變更一次性的變更到真實(shí) DOM 中,注意,這個(gè)階段里我們不再運(yùn)行創(chuàng)建 DOMrender ,因此,雖然我們一次性變更所有的 DOM ,但是性能來(lái)說(shuō)并不是太差。

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 節(jié)點(diǎn)沒(méi)什么必要操作
    return
  }

  // 拿到parent的原因是,我們要將元素插入的點(diǎn),插在父親的下面
  let domParentFiber = effectFiber.return
  while (domParentFiber.tag === tag.classComponent || domParentFiber.tag === tag.FunctionalComponent) {
    // 如果是 class 就直接跳過(guò),因?yàn)?class 類(lèi)型的fiber.stateNode 是其本身實(shí)例
    domParentFiber = domParentFiber.return
  }

  //拿到父親的真實(shí) DOM
  const domParent = domParentFiber.stateNode
  if (effectFiber.effectTag === PLACEMENT) {
    if (effectFiber.tag === tag.HostComponent || effectFiber.tag === tag.HostText) {
      //通過(guò) tag 檢查是不是真實(shí)的節(jié)點(diǎn)
      domParent.appendChild(effectFiber.stateNode)
    }
    // 其他情況
  } else if (effectFiber.effectTag == UPDATE) {
    // 更新邏輯 只能是沒(méi)實(shí)現(xiàn)
  } else if (effectFiber.effectTag == DELETION) {
    //刪除多余的舊節(jié)點(diǎn)
    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 的操作,值得注意的是,刪除操作依舊使用的鏈表操作。

最后來(lái)一段測(cè)試代碼:

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 (
      
hello luy
{this.state.info ? "imasync" : "iminfo"}
) } } render(, document.getElementById("root"))

我們來(lái)看看動(dòng)圖吧!當(dāng)節(jié)點(diǎn) mount 以后,過(guò)了 1 秒,就會(huì)更新,我們簡(jiǎn)單的更新就到此結(jié)束了


再看以下調(diào)用棧,我們的 requestIdleCallback 函數(shù)已經(jīng)正確的運(yùn)行了。

如果你想下載代碼親自體驗(yàn),可以到 Luy 倉(cāng)庫(kù)中:

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

目前我能找到的所有資料都放在倉(cāng)庫(kù)中:資料

回顧本文幾個(gè)重要的點(diǎn)

一開(kāi)始我們就使用了一個(gè)數(shù)組來(lái)記錄 update 的信息,通過(guò)調(diào)用 requestIdleCallback 來(lái)將更新一個(gè)一個(gè)的取出來(lái),大部分時(shí)間隊(duì)列里只有一個(gè)。

取出來(lái)以后,使用從左向右遍歷的方式,用鏈表鏈接一個(gè)一個(gè)的 Fiber ,并做 diff 和創(chuàng)建,最后一次性的 patch 到真實(shí) DOM 中去。

現(xiàn)在 react 的架構(gòu)已經(jīng)變得極其復(fù)雜,而本文也只是將 React 的整體架構(gòu)通篇流程描述了一遍,里面的細(xì)節(jié)依舊值得我們的深究,比如,如何傳遞 context ,如何實(shí)現(xiàn) ref ,如何實(shí)現(xiàn)錯(cuò)誤邊界處理,聲明周期的處理,這些都是很大的話(huà)題,在接下去的文章里,我會(huì)一步一步的將這些關(guān)系講清楚。

最后,感謝支持我的迷你框架項(xiàng)目:Luy ,現(xiàn)在正在向 Fiber 晉級(jí)!如果你喜歡,請(qǐng)給我一點(diǎn) star

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/95167.html

相關(guān)文章

  • React16性能改善的原理(二)

    摘要:接下來(lái)我們就是正式的工作了,用循環(huán)從某個(gè)節(jié)點(diǎn)開(kāi)始遍歷樹(shù)。最后一步判斷全局變量是否存在,如果存在則把這次遍歷樹(shù)產(chǎn)生的所有更新一次更新到真實(shí)的上去。 前情提要 上一篇我們提到如果 setState 之后,虛擬 dom diff 比較耗時(shí),那么導(dǎo)致瀏覽器 FPS 降低,使得用戶(hù)覺(jué)得頁(yè)面卡頓。那么 react 新的調(diào)度算法就是把原本一次 diff 的過(guò)程切分到各個(gè)幀去執(zhí)行,使得瀏覽器在 dif...

    guqiu 評(píng)論0 收藏0
  • 淺談React Fiber

    摘要:因?yàn)榘姹緦⒄嬲龔U棄這三生命周期到目前為止,的渲染機(jī)制遵循同步渲染首次渲染,更新時(shí)更新時(shí)卸載時(shí)期間每個(gè)周期函數(shù)各司其職,輸入輸出都是可預(yù)測(cè),一路下來(lái)很順暢。通過(guò)進(jìn)一步觀(guān)察可以發(fā)現(xiàn),預(yù)廢棄的三個(gè)生命周期函數(shù)都發(fā)生在虛擬的構(gòu)建期間,也就是之前。 showImg(https://segmentfault.com/img/bVbweoj?w=559&h=300); 背景 前段時(shí)間準(zhǔn)備前端招聘事項(xiàng)...

    izhuhaodev 評(píng)論0 收藏0
  • React Fiber 架構(gòu)理解

    摘要:架構(gòu)理解引用原文是核心算法正在進(jìn)行的重新實(shí)現(xiàn)。構(gòu)建的過(guò)程就是的過(guò)程,通過(guò)來(lái)調(diào)度執(zhí)行一組任務(wù),每完成一個(gè)任務(wù)后回來(lái)看看有沒(méi)有插隊(duì)的更緊急的,把時(shí)間控制權(quán)交還給主線(xiàn)程,直到下一次回調(diào)再繼續(xù)構(gòu)建。 React Fiber 架構(gòu)理解 引用原文:React Fiber ArchitectureReact Fiber is an ongoing reimplementation of Reacts...

    Berwin 評(píng)論0 收藏0
  • Deep In React之淺談 React Fiber 架構(gòu)(一)

    摘要:在上面我們已經(jīng)知道瀏覽器是一幀一幀執(zhí)行的,在兩個(gè)執(zhí)行幀之間,主線(xiàn)程通常會(huì)有一小段空閑時(shí)間,可以在這個(gè)空閑期調(diào)用空閑期回調(diào),執(zhí)行一些任務(wù)。另外由于這些堆棧是可以自己控制的,所以可以加入并發(fā)或者錯(cuò)誤邊界等功能。 文章首發(fā)于個(gè)人博客 前言 2016 年都已經(jīng)透露出來(lái)的概念,這都 9102 年了,我才開(kāi)始寫(xiě) Fiber 的文章,表示慚愧呀。不過(guò)現(xiàn)在好的是關(guān)于 Fiber 的資料已經(jīng)很豐富了,...

    Jiavan 評(píng)論0 收藏0
  • React 重要的一次重構(gòu):認(rèn)識(shí)異步渲染架構(gòu) Fiber

    摘要:在之前的叫,是新的,這次更新到架構(gòu)是一次重量級(jí)的核心架構(gòu)的替換,為了完成這次替換已經(jīng)準(zhǔn)備了兩三年的時(shí)間了。因此團(tuán)隊(duì)引入了異步渲染這個(gè)概念,而采用架構(gòu)可以實(shí)現(xiàn)這種異步渲染的方式。官方目前已經(jīng)把和標(biāo)記為,并使用新的生命周期函數(shù)和進(jìn)行替換。 Diff 算法 熟悉 react 的朋友都知道,在 react 中有個(gè)核心的算法,叫 diff 算法。web 界面由 dom 樹(shù)組成,不同的 dom 樹(shù)...

    cppowboy 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<