国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

React16性能改善的原理(二)

guqiu / 475人閱讀

摘要:接下來我們就是正式的工作了,用循環(huán)從某個節(jié)點開始遍歷樹。最后一步判斷全局變量是否存在,如果存在則把這次遍歷樹產(chǎn)生的所有更新一次更新到真實的上去。

前情提要

上一篇我們提到如果 setState 之后,虛擬 dom diff 比較耗時,那么導(dǎo)致瀏覽器 FPS 降低,使得用戶覺得頁面卡頓。那么 react 新的調(diào)度算法就是把原本一次 diff 的過程切分到各個幀去執(zhí)行,使得瀏覽器在 diff 過程中也能響應(yīng)用戶事件。接下來我們具體分析下新的調(diào)度算法是怎么回事。

原虛擬DOM問題

假設(shè)我們有一個 react 應(yīng)用如下:

class App extends React.Component {
  render() {
    return (
      
{this.props.name}
  • {this.props.items[0]}
  • {this.props.items[1]}
); } }

整個 app 的虛擬 dom 大致是這樣的:

var rootHost = {
  type: "div",
  children: [ {
    type: "div",
    children: [ {type: "text"} ]
  }. {
    type: "ul",
    children: [
      { type: "li", children:[ {type: "text"} ] },
      { type: "li", children:[ {type: "text"} ] }
    ]
  } ]
}

當(dāng)更新發(fā)生 diff 兩棵新老虛擬 dom 樹的時候是遞歸的逐層比較(如下圖)。這個過程是一次完成的,如果要按上一篇我們說的把 diff 過程切割成好多時間片來執(zhí)行,難度是如何記住狀態(tài)且恢復(fù)現(xiàn)場。譬如說你 diff 到一半函數(shù)返回了,等下一個時間片繼續(xù) diff。如果只記住上次遞歸到哪個節(jié)點,那么你只能順著他的 children 繼續(xù) diff,而它的兄弟節(jié)點就丟失了。如果要完美恢復(fù)現(xiàn)場保存的結(jié)構(gòu)估計得挺復(fù)雜。所以 react16 改造了虛擬dom的結(jié)構(gòu),引入了 fiber 的鏈表結(jié)構(gòu)。

現(xiàn)在解決方案 - fiber

fiber 節(jié)點相當(dāng)于以前的虛擬 dom 節(jié)點,結(jié)構(gòu)如下:

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

先講重要的幾個屬性: return 存儲的是當(dāng)前節(jié)點的父節(jié)點(元素),child 存儲的是第一個子節(jié)點(元素),sibling 存儲的是他右邊第一個的兄弟節(jié)點(元素)。alternate 保存是當(dāng)更新發(fā)生時候同一個節(jié)點帶有新的 props 和 state 生成的新 fiber 節(jié)點。 那么虛擬 dom 的存儲結(jié)構(gòu)用鏈表的形式描述了整棵樹。

從頂層開始左序深度優(yōu)先遍歷如下圖所示:

我們在遍歷 dom 樹 diff 的時候,即使中斷了,我們只需要記住中斷時候的那么一個節(jié)點,就可以在下個時間片恢復(fù)繼續(xù)遍歷并 diff。這就是 fiber 數(shù)據(jù)結(jié)構(gòu)選用鏈表的一大好處。我先用文字大致描述下 fiber diff 算法的過程再來看代碼。從跟節(jié)點開始遍歷,碰到一個節(jié)點和 alternate 比較并記錄下需要更新的東西,并把這些更新提交到當(dāng)前節(jié)點的父親。當(dāng)遍歷完這顆樹的時候,再通過 return 回溯到根節(jié)點。這個過程中把所有的更新全部帶到根節(jié)點,再一次更新到真實的 dom 中去。

從根節(jié)點開始:

div1 通過 child 到 div2。

div2 和自己的 alternate 比較完把更新 commit1 通過 return 提交到 div1。

div2 通過 sibling 到 ul1。

ul1 和自己的 alternate 比較完把更新 commit2 通過 return 提交到 div1。

ul1 通過 child 到 li1。

li1 和自己的 alternate 比較完把更新 commit3 通過 return 提交到 ul1。

li1 通過 sibling 到 li2。

li2 和自己的 alternate 比較完把更新 commit4 通過 return 提交到 ul1。

遍歷完整棵樹開始回溯,li2 通過 return 回到 ul1。

把 commit3 和 commit4 通過 return 提交到 div1。

ul1 通過 return 回到 div1。

獲取到所有更新 commit1-4,一次更新到真是的 dom 中去。

使用fiber算法更新的代碼實現(xiàn)
React.Component.prototype.setState = function( partialState, callback ) {
  updateQueue.pus( {
    stateNode: this,
    partialState: partialState
  } );
  requestIdleCallback(performWork); // 這里就開始干活了
}

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

setState 先把此次更新放到更新隊列 updateQueue 里面,然后調(diào)用調(diào)度器開始做更新任務(wù)。performWork 先調(diào)用 workLoop 對 fiber 樹進行遍歷比較,就是我們上面提到的遍歷過程。當(dāng)此次時間片時間不夠遍歷完整個 fiber 樹,或者遍歷并比較完之后 workLoop 函數(shù)結(jié)束。接下來我們判斷下 fiber 樹是否遍歷完或者更新隊列 updateQueue 是否還有待更新的任務(wù)。如果有則調(diào)用 requestIdleCallback 在下個時間片繼續(xù)干活。nextUnitOfWork 是個全局變量,記錄 workLoop 遍歷 fiber 樹中斷在哪個節(jié)點。

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

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

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

剛開始遍歷的時候判斷全局變量 nextUnitOfWork 是否存在?如果存在表示上次任務(wù)中斷了,我們繼續(xù),如果不存在我們就從更新隊列里面取第一個任務(wù),并生成對應(yīng)的 fiber 根節(jié)點。接下來我們就是正式的工作了,用循環(huán)從某個節(jié)點開始遍歷 fiber 樹。performUnitOfWork 根據(jù)我們上面提到的遍歷規(guī)則,在對當(dāng)前節(jié)點處理完之后,返回下一個需要遍歷的節(jié)點。循環(huán)除了要判斷是否有下一個節(jié)點(是否遍歷完),還要判斷當(dāng)前給你的時間是否用完,如果用完了則需要返回,讓瀏覽器響應(yīng)用戶的交互事件,然后再在下個時間片繼續(xù)。workLoop 最后一步判斷全局變量 pendingCommit 是否存在,如果存在則把這次遍歷 fiber 樹產(chǎn)生的所有更新一次更新到真實的 dom 上去。注意 pendingCommit 在完成一次完整的遍歷過程之前是不會有值的。

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
}

createWorkInProgress 拿出更新隊列 updateQueue 第一個任務(wù),然后看觸發(fā)這個任務(wù)的節(jié)點是什么類型。如果不是根節(jié)點,則通過循環(huán)迭代節(jié)點的 return 找到最上層的根節(jié)點。最后生成一個新的 fiber 節(jié)點,這個節(jié)點就是當(dāng)前 fiber 節(jié)點的 alternate 指向的,也就是說下面會在當(dāng)前節(jié)點和這個新生成的節(jié)點直接進行 diff。

function performUnitOfWork(workInProgress) {
  const nextChild = beginWork(workInProgress)
  if (nextChild) return nextChild

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

performUnitOfWork 做的工作是 diff 當(dāng)前節(jié)點,diff 完看看有沒有子節(jié)點,如果沒有子節(jié)點則把更新先提交到父節(jié)點。然后再看有沒有兄弟節(jié)點,如果有則返回出去當(dāng)作下次遍歷的節(jié)點。如果還是沒有,說明整個 fiber 樹已經(jīng)遍歷完了,則進入到回溯過程,把所有的更新都集中到根節(jié)點進行更新真實 dom。

function completeWork(currentFiber) {
  if (currentFiber.tag === tag.classComponent) {
    // 用于回溯最高點的 root
    currentFiber.stateNode._internalfiber = currentFiber
  }

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

我們看到 completeWork 中當(dāng)判斷到當(dāng)前節(jié)點是根節(jié)點的時候才賦值 pendingCommit 整個全局變量。

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

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

當(dāng)回溯完,有了 pendingCommit,則 commitAllwork 會被調(diào)用。它做的工作就是循環(huán)遍歷根節(jié)點的 effets 數(shù)據(jù),里面保存著所有要更新的內(nèi)容。commitWork 就是執(zhí)行具體更新的函數(shù),這里就不展開了(因為這篇主要想講的是 fiber 更新的調(diào)度算法)。

所以你們看遍歷 dom 數(shù) diff 的過程是可以被打斷并且在后續(xù)的時間片上接著干,只是最后一步 commitAllwork 是同步的不能打斷的。這樣 react 使用新的調(diào)度算法優(yōu)化了更新過程中執(zhí)行時間過長導(dǎo)致的頁面卡頓現(xiàn)象。

參考文獻(xiàn)

為 Luy 實現(xiàn) React Fiber 架構(gòu) - 更詳細(xì)的代碼實現(xiàn)可以看這片文章。

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

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/109170.html

相關(guān)文章

  • React16性能改善原理

    摘要:接下來看下偽代碼調(diào)度算法偽代碼原來這段寫的匆忙且不好,重新更新了一篇講調(diào)度算法的大概實現(xiàn)性能改善的原理二。 問題背景 React16 更新了底層架構(gòu),新架構(gòu)主要解決更新節(jié)點過多時,頁碼卡頓的問題。譬如如下代碼,根據(jù)用戶輸入的文字生成10000行數(shù)據(jù),用戶輸入框會出現(xiàn)卡頓現(xiàn)象。 class App extends React.Component { constructor( prop...

    zhangqh 評論0 收藏0
  • webpack打包分析與性能優(yōu)化

    摘要:打包分析與性能優(yōu)化背景在去年年末參與的一個項目中,項目技術(shù)棧使用,生產(chǎn)環(huán)境全量構(gòu)建將近三分鐘,項目業(yè)務(wù)模塊多達(dá)數(shù)百個,項目依賴數(shù)千個,并且該項目協(xié)同前后端開發(fā)人員較多,提高構(gòu)建效率,成為了改善團隊開發(fā)效率的關(guān)鍵之一。 webpack打包分析與性能優(yōu)化 背景 在去年年末參與的一個項目中,項目技術(shù)棧使用react+es6+ant-design+webpack+babel,生產(chǎn)環(huán)境全量構(gòu)建將...

    joy968 評論0 收藏0
  • 王下邀月熊_Chevalier前端每周清單系列文章索引

    摘要:感謝王下邀月熊分享的前端每周清單,為方便大家閱讀,特整理一份索引。王下邀月熊大大也于年月日整理了自己的前端每周清單系列,并以年月為單位進行分類,具體內(nèi)容看這里前端每周清單年度總結(jié)與盤點。 感謝 王下邀月熊_Chevalier 分享的前端每周清單,為方便大家閱讀,特整理一份索引。 王下邀月熊大大也于 2018 年 3 月 31 日整理了自己的前端每周清單系列,并以年/月為單位進行分類,具...

    2501207950 評論0 收藏0
  • 性能迷你React框架anujs1.1.4發(fā)布

    摘要:本周在支持機票的項目中對做了大量改進,包括性能上與結(jié)構(gòu)上的改進。但通過一些簡化改改良,代碼的可靠性大大提高了。此外,還有周邊的優(yōu)化在目錄下提供一個,用于在舊式中替換。改善,里面內(nèi)置了一個補丁,也是用于改善性能,或中的性能好差。 本周在支持機票的項目中對anujs做了大量改進,包括性能上與結(jié)構(gòu)上的改進。與1.1.3一樣,還是差一個組件就完全兼容阿里的antd UI庫。 框架本身的改進有:...

    elva 評論0 收藏0

發(fā)表評論

0條評論

guqiu

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<