<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 Fiber架構(gòu)淺析

          共 24819字,需瀏覽 50分鐘

           ·

          2021-09-12 00:58

          大廠技術(shù)  堅持周更  精選好文

          1.瀏覽器渲染

          為了更好的理解 React Fiber, 我們先簡單了解下渲染器進(jìn)程的內(nèi)部工作原理。

          參考資料:

          1. 從內(nèi)部了解現(xiàn)代瀏覽器(3)[1]
          2. 渲染樹構(gòu)建、布局及繪制[2]

          1.1 渲染幀

          幀 (frame): 動畫過程中,每一幅靜止的畫面叫做幀。

          幀率 (frame per second): 即每秒鐘播放的靜止畫面的數(shù)量。

          幀時長 (frame running time): 每一幅靜止的畫面的停留時間。

          丟幀 (dropped frame): 當(dāng)某一幀時長高于平均幀時長。

          • 一般來說瀏覽器刷新率在60Hz, 渲染一幀時長必須控制在16.67ms (1s / 60 = 16.67ms)。
          • 如果渲染超過該時間, 對用戶視覺上來說,會出現(xiàn)卡頓現(xiàn)象,即丟幀 (dropped frame)。

          1.2 幀生命周期


          圖: 簡單描述幀生命周期

          簡單描述一幀的生命周期:

          1. 一幀開始。

          2. 主線程:

          - Event Handlers: UI交互輸入的事件回調(diào), 例如input、click、wheel等。

          - RAF: 執(zhí)行requestAnimationFrame回調(diào)。

          - DOM Tree: 解析HTML, 構(gòu)建DOM Tree, 當(dāng)JS對DOM有變更會重新觸發(fā)該流程。

          - CSS Tree: 構(gòu)建CSS Tree。至此構(gòu)建出Render Tree。

          - Layout: 所有元素的position、size信息。

          - Paint: 像素填充, 例如顏色、文字、邊框等可視部分。

          - Composite: 繪制的指令信息傳到合成線程中。

          - RequestIdleCallback: 如果此時一幀還有空余時間, 則執(zhí)行該回調(diào)。

          3. 合成線程:

          - Raster: 合成線程將信息分塊, 并把每塊發(fā)送給光柵線程, 光柵線程創(chuàng)建位圖, 并通知GPU進(jìn)程刷新這一幀。

          4. 一幀結(jié)束。

          1.3 丟幀實驗

          怎么就丟幀了呢?

          對于流暢的動畫,如果一幀處理時間超過16ms,就能感到頁面的卡頓了。

          Demo: https://linjiayu6.github.io/FE-RequestIdleCallback-demo/

          Github: RequestIdleCallback 實驗[3]

          當(dāng)用戶點(diǎn)擊任一按鍵 A,B,C,因為主線程執(zhí)行Event Handlers任務(wù),動畫因為瀏覽器不能及時處理下一幀,導(dǎo)致動畫出現(xiàn)卡頓的現(xiàn)象。

          // 處理同步任務(wù),并占用主線程

          const bindClick = id =>

          element(id).addEventListener('click', Work.onSyncUnit)

          // 綁定click事件

          bindClick('btnA')

          bindClick('btnB')

          bindClick('btnC')

          var Work = {

          // 有1萬個任務(wù)

          unit10000,

          // 處理每個任務(wù)

          onOneUnitfunction (for (var i = 0; i <= 500000; i++) {} },

          // 同步處理: 一次處理完所有任務(wù)

          onSyncUnitfunction ({

          let _u = 0

          while (_u < Work.unit) {

          Work.onOneUnit()

          _u ++

          }

          }

          }


          1.4 解決丟幀

          上述,我們發(fā)現(xiàn) JS運(yùn)算是占用渲染的時間的。

          在連續(xù)動畫中,要做高耗時的操作,如何保證幀平穩(wěn)呢?

          解決丟幀思考如下:

          1. 在一幀空閑時處理, 利用 RequestIdleCallback[4] 處理任務(wù)。

          window.requestIdleCallback()方法將在瀏覽器的空閑時段內(nèi)調(diào)用的函數(shù)排隊。這使開發(fā)者能夠在主事件循環(huán)上執(zhí)行后臺和低優(yōu)先級工作,而不會影響延遲關(guān)鍵事件,如動畫和輸入響應(yīng)。函數(shù)一般會按先進(jìn)先調(diào)用的順序執(zhí)行,然而,如果回調(diào)函數(shù)指定了執(zhí)行超時時間timeout,則有可能為了在超時前執(zhí)行函數(shù)而打亂執(zhí)行順序。

          1. 對高耗時的任務(wù),進(jìn)行分步驟處理。
          1. Web worker 貌似也可以解決上述問題,這里不做擴(kuò)展。
          2. ...

          這里我們利用 RequestIdleCallback[5] 做個實驗咩。

          Demo: https://linjiayu6.github.io/FE-RequestIdleCallback-demo/

          Github: RequestIdleCallback 實驗[6]

          const bindClick = id =>

          element(id).addEventListener('click', Work.onAsyncUnit)

          // 綁定click事件

          bindClick('btnA')

          bindClick('btnB')

          bindClick('btnC')

          var Work = {

          // 有1萬個任務(wù)

          unit10000,

          // 處理每個任務(wù)

          onOneUnitfunction (for (var i = 0; i <= 500000; i++) {} },

          // 異步處理

          onAsyncUnitfunction ({

          // 空閑時間 1ms

          const FREE_TIME = 1

          let _u = 0

          function cb(deadline{

          // 當(dāng)任務(wù)還沒有被處理完 & 一幀還有的空閑時間 > 1ms

          while (_u < Work.unit && deadline.timeRemaining() > FREE_TIME) {

          Work.onOneUnit()

          _u ++

          }

          // 任務(wù)干完, 執(zhí)行回調(diào)

          if (_u >= Work.unit) {

          // 執(zhí)行回調(diào)

          return

          }

          // 任務(wù)沒完成, 繼續(xù)等空閑執(zhí)行

          window.requestIdleCallback(cb)

          }

          window.requestIdleCallback(cb)

          }

          }


          requestIdleCallback 啟發(fā)

          將一個大任務(wù)分割成N個小任務(wù),在每一幀有空余時間情況下,逐步去執(zhí)行小任務(wù)。

          2.React15 (-) 架構(gòu)缺點(diǎn)

          React: stack reconciler實現(xiàn)[7]

          React 算法之深度優(yōu)先遍歷[8]

          遞歸 Recursion: 利用 調(diào)用棧[9],實現(xiàn)自己調(diào)用自己的方法。

          最常見的就是 Leetcode: 斐波拉契數(shù)列[10]Leetcode: 70. 爬樓梯[11]

          2.1 概述原因

          該情況,類似我們上述# 1.3丟幀實驗。

          2.2 流程和代碼解析

          可能需要你有點(diǎn) 深度優(yōu)先遍歷、遞歸、回溯思想、?? 等數(shù)據(jù)結(jié)構(gòu)的知識。

          這里只做流程解析,代碼也為閹割版,重點(diǎn)是理解思想哈。

          某React節(jié)點(diǎn)如下:

            class A extends React.Component {

              ...



              render() {

                return (

                  <div id="app">

                    <h1></h1>

                    <p><h2></h2></p>

                    <h3></h3>

                  </div>


                )

              }

           }

          圖 DFS + 遞歸遍歷的路徑

          下面是 ReactFiberWorkLoop.old.js[12] 閹割版代碼,為了簡要說明該流程。

          // 工作循環(huán)同步處理

          function workLoopSync({

            // 有任務(wù)

            while (workInProgress !== null) {

              performUnitOfWork(workInProgress);

            }

          }



          function performUnitOfWork(unitOfWork: Fiber): void {

            // 對該節(jié)點(diǎn) 開始工作: return workInProgress.child; 返回的是該節(jié)點(diǎn)的孩子

            let next = beginWork(...);



            if (next === null) {

              // 對某Node 完成工作: 回溯向上, 向上找到某節(jié)點(diǎn)的兄弟 sibling 或 直到向上為root代表, 遍歷結(jié)束。

              completeUnitOfWork(unitOfWork);

            } else {

              // 從ta 孩子入手, 繼續(xù)向下工作

              workInProgress = next;

            }

          }



          /**

           * siblingFiber: 兄弟節(jié)點(diǎn)

           * returnFiber: 父親節(jié)點(diǎn)

           */


          function completeUnitOfWork(unitOfWork: Fiber): void {

            let completedWork = unitOfWork;



            // 這里又是一個循環(huán)

            do {

              // 1. 判斷任務(wù)是否完成, 完成就打個完成的標(biāo)簽, 沒有完成就拋出異常



              // 2. 如果有兄弟節(jié)點(diǎn), 那么接下來工作節(jié)點(diǎn)是該 xd

              if (completedWork.sibling !== null) {

                workInProgress = siblingFiber;

                return;

              }



              // 3. 否則, 返回父親節(jié)點(diǎn)

              completedWork = completedWork.return;

              workInProgress = completedWork;

            } while (completedWork !== null);



            // 最后, 是root節(jié)點(diǎn), 結(jié)束

            if (workInProgressRootExitStatus === RootIncomplete) {

              workInProgressRootExitStatus = RootCompleted;

            }

          }

          3.上述總結(jié)

          因果關(guān)系

          基于這些原因,React不得不重構(gòu)整個框架。

          1. React (15ver-) 對創(chuàng)建和更新節(jié)點(diǎn)的處理,是通過 遞歸 ??。

          2. 遞歸 , 在未完成對整個?? 的遍歷前,是不會停止的。

          3. 該 任務(wù) 一直占用瀏覽器主線程,導(dǎo)致無 響應(yīng)優(yōu)先級更高 的任務(wù)。

          4. 故,瀏覽器渲染超過臨界時間,從視覺上來看,卡死 ??。

          主動思考

          為了快速響應(yīng),防止丟幀,解決思路:


          1. 將 任務(wù) 分解成 N個小任務(wù);

          2. If 一幀里沒有 優(yōu)先級更高的任務(wù),則執(zhí)行自己。

             else 有其他 優(yōu)先級高的事務(wù), 優(yōu)先執(zhí)行其他。

               If 等一幀有 空閑 再執(zhí)行自己。

               else 下一幀。

          我們再回頭看下這個圖,問題即轉(zhuǎn)換如下:

          如何將任務(wù)拆分?

          如何判斷優(yōu)先級?

          如何判斷一幀空閑時,再執(zhí)行?

          ...

          Fiber 架構(gòu)

          推薦 ?? https://github.com/7kms/react-illustration-series/tree/v17.0.1

          推薦 ?? https://react.iamkasong.com/preparation/oldConstructure.html

          下面,不會有大段大段代碼,去講具體的實現(xiàn)。

          而是,以因果邏輯,帶你去了解 why,how,when (為什么、怎么做、何時做)。

          4.抽象問題

          上面我們說到了什么任務(wù)、優(yōu)先級等等,我們通過圖的方式,抽象下問題。

          描述:

          1. 任務(wù)A進(jìn)入執(zhí)行區(qū)域。

          2. 在執(zhí)行任務(wù)A的過程中,更高優(yōu)先級任務(wù)B,請求被執(zhí)行。

          3. 但因為先來后到嘛,此時任務(wù)B因為無法被執(zhí)行,而暫時被掛起,只能等待執(zhí)行。

          4. 只有執(zhí)行完任務(wù)A后,才會執(zhí)行任務(wù)B。

          上述流程可類比:  你在吃飯,突然你老板 給你打電話,你一定要堅持吃完飯,才接你老板的電話。

          (腦補(bǔ)一下老板的表情??)

          很明顯,這樣處理問題,效率奇低無比。

          按照我們在前情總結(jié)部分的訴求,將上述圖變成這樣是不是更合理些。

          描述:

          1. 任務(wù)A進(jìn)入執(zhí)行區(qū)域。

          2. 在執(zhí)行任務(wù)A的過程中,更高優(yōu)先級任務(wù)B,請求被執(zhí)行。

          3. 考慮到任務(wù)B優(yōu)先級更高,則將任務(wù)A沒有執(zhí)行完成的部分,Stash暫存。

          4. 任務(wù)B被執(zhí)行。當(dāng)任務(wù)B被執(zhí)行完成后,去執(zhí)行剩余沒有完成的任務(wù)A。

          上述流程可類比:  你在吃飯,突然你老板給你打電話,即使你沒有吃完飯,也接起了你老板的電話,后繼續(xù)吃飯。(腦補(bǔ)一下老板的表情??)

          5.核心關(guān)注

          5.1 并發(fā)、調(diào)度

          Concurrency & Scheduler

          Concurrency 并發(fā):  有能力優(yōu)先處理更高優(yōu)事務(wù),同時對正在執(zhí)行的中途任務(wù)可暫存,待高優(yōu)完成后,再去執(zhí)行。

          concurrency is the ability of different parts or units of a program[13]algorithm[14], or problem[15] to be [executed](https://en.wikipedia.org/wiki/Execution_(computing "executed")) out-of-order or at the same time simultaneously partial order[16], without affecting the final outcome.

          https://en.wikipedia.org/wiki/Concurrency_(computer_science)

          Scheduler 協(xié)調(diào)調(diào)度: 暫存未執(zhí)行任務(wù),等待時機(jī)成熟后,再去安排執(zhí)行剩下未完成任務(wù)。

          考慮 所有任務(wù)可以被并發(fā)執(zhí)行,就需要有個協(xié)調(diào)任務(wù)的調(diào)度算法。

          看到這里,不知道你有沒有發(fā)現(xiàn)一個大bug。

          肯定是Call Stack[17]

          5.2 調(diào)用棧、虛擬調(diào)用棧幀

          調(diào)用棧這里看起來就很不合理。

          因為瀏覽器是利用調(diào)用棧來管理函數(shù)執(zhí)行順序的,秉承著先進(jìn)后出原則,是如何做到某任務(wù)都入棧了,但是因為中途有其他事兒,就被中斷。中斷就不算了,還能中斷后,接著后續(xù)再執(zhí)行。

          問題突然間就變成: pause a functioin call (暫停對一個函數(shù)的調(diào)用)。

          巧了,像 generator 和 瀏覽器debugger 就可以做到中斷函數(shù)調(diào)用。但考慮到可中斷渲染,并可重回構(gòu)造。React自行實現(xiàn)了一套體系叫做 React fiber 架構(gòu)。

          React Fiber 核心: 自行實現(xiàn) 虛擬棧幀。

          That's the purpose of React Fiber. Fiber is reimplementation of the stack, specialized for React components. You can think of a single fiber as a virtual stack frame.

          https://github.com/acdlite/react-fiber-architecture

          看到這里,是不是覺得 React yyds。ps: 反正看不太懂的都是 yyds。

          5.3 React 16 (+) 架構(gòu)

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

          FiberNode.js[18]

          Fiber的數(shù)據(jù)結(jié)構(gòu)有三層信息: 實例屬性、構(gòu)建屬性、工作屬性。

          下面以該demo代碼為例:

          <div id="linjiayu">123</div>

          <script type="text/babel">

              const App = () => {

                  const [sum, onSetSum] = React.useState(0)



                  return (

                      <div id="app 1">

                          <h1 id="2-1 h1">標(biāo)題 h1</h1>

                          <ul id="2-2 ul"> 

                              <li id="3-1 li" onClick={() => onSetSum(d => d + 1)}>點(diǎn)擊 h2</li>

                              <li id="3-2 li">{sum}</li>

                          </ul>



                          <h3 id="2-3 h3">標(biāo)題 h3</h3>

                      </div>


                  )

              }



              ReactDOM.render(

                  <App />,

                  document.getElementById('linjiayu')

              );

          </script>

          6.1 實例屬性

          該Fiber的基本信息,例如組件類型等。

          6.2 構(gòu)建屬性

          構(gòu)建屬性 (return、child、sibling),根據(jù)上面代碼,我們構(gòu)建一個Fiber樹??。

          構(gòu)建流程

          和 2.2 流程和代碼解析 部分不同的是:

          1. 分為同步或異步更新。
          2. 且增加的異步更新 使用該字段 shouldYield 來判斷是否需要中斷。
          // performSyncWorkOnRoot會調(diào)用該方法

          function workLoopSync({

            while (workInProgress !== null) {

              performUnitOfWork(workInProgress);

            }

          }



          // performConcurrentWorkOnRoot會調(diào)用該方法

          function workLoopConcurrent({

            while (workInProgress !== null && ! shouldYield ()) {

              performUnitOfWork(workInProgress);

            }

          }

          在一個遞歸循環(huán)里,遞: beginWork()[19], 歸 completeWork()[20]

          虛線: 表達(dá)構(gòu)建關(guān)系,但未完成狀態(tài)。

          實線: 已構(gòu)建關(guān)系,并已執(zhí)行某個狀態(tài)。

          • 實線 child 和 sibling 已執(zhí)行beginWork()
          • 實線 return 已執(zhí)行 completeUnitOfWork()
          1. 創(chuàng)建fiberNode FiberRootNode 

          2. 創(chuàng)建fiberNode rootFiber (即示例中 <div id="linjiayu">)



          進(jìn)入循環(huán)工作區(qū)域, workInProgress(工作指針指向 rootFiber)

          3. 創(chuàng)建fiberNode App 

             beginWork() -> 只有一個子節(jié)點(diǎn) -> workInProgress(工作指針指向App) 

             

          4. 創(chuàng)建fiberNode div 

             beginWork() -> 有多個子節(jié)點(diǎn) -> workInProgress(工作指針指向div) 
          5. 構(gòu)建孩子們節(jié)點(diǎn)

          按照5.1 -> 5.2 -> 5.3 順序?qū)⒚總€節(jié)點(diǎn)創(chuàng)建。
          6. workInProgress (工作指針指向h1)

             beginWork() -> 沒有子節(jié)點(diǎn) -> completeUnitOfWork() -> 有兄弟節(jié)點(diǎn),繼續(xù) ...

          6.3 工作屬性

          1. 【數(shù)據(jù)】數(shù)據(jù)的變更會導(dǎo)致UI層的變更。
          2. 【協(xié)調(diào)】為了減少對DOM的直接操作,通過Reconcile進(jìn)行diff查找,并將需要變更節(jié)點(diǎn),打上標(biāo)簽,變更路徑保留在effectList里。
          3. 【調(diào)度】待變更內(nèi)容要有Scheduler優(yōu)先級處理。

          故,涉及到diff等查找操作,是需要有個高效手段來處理前后變化,即雙緩存機(jī)制。

          有關(guān)雙緩存機(jī)制、數(shù)據(jù)更新、diff算法等,這里不做過多介紹。

          7.Reconciler 和 Scheduler

          上面,我們概述了fiberNode的數(shù)據(jù)結(jié)構(gòu),鏈表結(jié)構(gòu)即可支持隨時隨時中斷的訴求

          下面我們簡述下架構(gòu)中兩個核心模塊:

          • Reconciler (協(xié)調(diào)): 負(fù)責(zé)找出變化的組件。
          • Scheduler (調(diào)度): 負(fù)責(zé)找出高優(yōu)任務(wù)。

          7.1 Reconciler 運(yùn)行流程淺析

          1. 【輸入】  當(dāng)數(shù)據(jù)初始化或變化,最后會調(diào)用schedulerUpdateOnFiber該方法。
          • 不需要調(diào)度,直接去構(gòu)造fiber樹。
          • 需要調(diào)度,注冊調(diào)度任務(wù)。
          // scheduleUpdateOnFiber(fiber, lane, eventTime) 以下為閹割版代碼

          // 同步

          if (lane === SyncLane) {

              if ( 

                 // Check if we're inside unbatchedUpdates (沒有一次事件回調(diào)中觸發(fā)多次更新)

                (executionContext & LegacyUnbatchedContext) !== NoContext && 

                // Check if we're not already rendering (是否尚未渲染)

                (executionContext & (RenderContext | CommitContext)) === NoContext) {

                // 不調(diào)度, 直接去構(gòu)造fiber樹

                performSyncWorkOnRoot(root);

             }

          }



          // 否則,需要調(diào)度交給Scheduler后,再去構(gòu)造fiber樹

          ensureRootIsScheduled(root, eventTime);
          1. 【注冊任務(wù)】  ensureRootIsScheduled

          兩類任務(wù):

          • performSyncWorkOnRoot 同步構(gòu)建tree。
          • performConcurrentWorkOnRoot 異步構(gòu)建tree。

          scheduleSyncCallback 或 scheduleCallback: 將上述兩類任務(wù)封裝到了對應(yīng)的任務(wù)隊列中。

          // ensureRootIsScheduled

          function ensureRootIsScheduled(root, currentTime{

              // ....

              

              // 1. 優(yōu)先級最高,立刻馬上要同步執(zhí)行

              if (newCallbackPriority === SyncLanePriority) {

                newCallbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));

               // 2. 同步批量更新

              } else if (newCallbackPriority === SyncBatchedLanePriority) {

                newCallbackNode = scheduleCallback(ImmediatePriority$1, performSyncWorkOnRoot.bind(null, root));

              } else {

                // 3. 異步優(yōu)先級登記

                var schedulerPriorityLevel = lanePriorityToSchedulerPriority(newCallbackPriority);

                newCallbackNode = scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));

              }

              

              // ...

              

              // 更新rootFiber 任務(wù)

              root.callbackNode = newCallbackNode;

          }

          同步任務(wù)會放到syncQueue 隊列,會被立即被執(zhí)行。

          var _queue = syncQueue;



          // 執(zhí)行所有同步任務(wù)

          runWithPriority(ImmediatePriority, () => {

              for (; i < queue.length; i++) {

              let callback = queue[i];

              do {

                  callback = callback(isSync);

              } while (callback !== null);

              }

          });

          // 清空同步任務(wù)

          syncQueue = null;

          異步處理會調(diào)用 scheduler方法 unstable_scheduleCallback,其實是requestIdleCallback替代品,該方法傳入回調(diào)任務(wù),和過期時間,來安排任務(wù)的執(zhí)行。

          function unstable_scheduleCallback(callback, deprecated_options{}
          1. 【執(zhí)行任務(wù)回調(diào)】

          下面 performSyncWorkOnRoot 和 performConcurrentWorkOnRoot 不同的是: 異步執(zhí)行任務(wù),可隨時中斷渲染 shouldYield()

          同步執(zhí)行構(gòu)建樹

          function performSyncWorkOnRoot(root{

            // 1. 構(gòu)建樹

            /*

              renderRootSync 會 調(diào)用該方法 workLoopSync

              while (workInProgress !== null) {

                performUnitOfWork(workInProgress);

              }

            */


            renderRootSync(root, lanes)

            

            // 2. 輸出樹 (可看下雙緩存機(jī)制)

            finishedWork = root.current.alternate;

          }

          異步執(zhí)行構(gòu)建樹

          function performConcurrentWorkOnRoot(root{

             // 1. 構(gòu)建樹

             /*

              renderRootConcurrent 會 調(diào)用該方法 workLoopConcurrent

              while (workInProgress !== null &&  !shouldYield() ) {

                performUnitOfWork(workInProgress);

              }

            */


             renderRootConcurrent(root, lanes);

             // 2. 輸出樹 (可看下雙緩存機(jī)制)

             finishConcurrentRender(root, exitStatus, lanes);

             

             // 3. check 是否還有其他更新, 是否需要發(fā)起新調(diào)度

             ensureRootIsScheduled(root, now());

              if (root.callbackNode === originalCallbackNode) {

                // 當(dāng)前執(zhí)行的任務(wù)被中斷,返回個新的,再次渲染。

                return performConcurrentWorkOnRoot.bind(null, root);

              }



              return null;

          }
          1. 輸出

          將變更內(nèi)容,輸出至界面。詳細(xì)看 commitRoot方法的實現(xiàn)。這里不做擴(kuò)展。

          1. 小總結(jié)

          7.2 Scheduler 運(yùn)行流程淺析

          workloop.js[21]

          上面我們說到了同步和異步的任務(wù),異步任務(wù)是可以中斷且需要Scheduler配合處理。

          注意只有異步任務(wù)即開啟了并發(fā)模式,才會有時間分片。

          workLoop是 實現(xiàn)時間切片 和 可中斷渲染的核心。也是我們上面說到的虛擬棧幀的能力 

          以下為了說明,簡化流程:

          // 并發(fā)任務(wù)的入口

          function workLoopConcurrent({

            // Perform work until Scheduler asks us to yield

            // 有任務(wù) & 是否需要中斷

            while (workInProgress !== null && !shouldYield() ) {

              performUnitOfWork(workInProgress);

            }

          }

          const scheduler = {

              // 任務(wù)放到隊列里,等待空閑執(zhí)行

              taskQueue: [

                 {

                    // 每個任務(wù)是個回調(diào)的概念, 且回調(diào)任務(wù)是可中斷的

                    callback: workLoopConcurrent

                 }

              ],


              // 判斷: 是否需要中斷, 將控制權(quán)交給主進(jìn)程

              shouldYieldToHost () {

                  // 沒有剩余時間

                  if (currentTime >= deadline) {

                      // 但需要渲染 和 有更高優(yōu)任務(wù)

                      if (needsPaint || scheduling.isInputPending()) {

                          return true// 中斷

                      }

                      // 是否超過 300ms

                      return currentTime >= maxYieldInterval;

                  }


                  // 還有剩余時間

                  return false;

              },


              // 執(zhí)行入口可見

              workLoop () {

                  // 當(dāng)前第一個任務(wù)

                  currentTask = taskQueue[0];

           
                  // 每次 currentTask 退出 就是一個時間切切片

                  while(currentTask !== null) {

                      // 任務(wù)沒有過期, 但一幀已經(jīng)無可用時間 或 需要被中斷, 則讓出主線程

                      // 每一次執(zhí)行均進(jìn)行超時檢測,做到讓出主線程。

                      if (currentTask.expirationTime > currentTime

           && (!hasTimeRemaining || shouldYieldToHost())) {

           break

           }

                      // 執(zhí)行任務(wù)

                      const callback = currentTask.callback;

                      const continuationCallback = callback(didUserCallbackTimeout);

                      // 如果該任務(wù)后, 還有連續(xù)回調(diào)

                      if (typeof continuationCallback === 'function') {

                          // 則保留當(dāng)前

                          currentTask.callback = continuationCallback;

                      } else  {

                          // 將currentTask移除該隊列

                          pop(taskQueue);

                      }


                      // 更新currentTask

                      currentTask = peek(taskQueue);

                  }

              },

          }

          簡而言之:

          1. 有個任務(wù)隊列 queue,該隊列存放可中斷的任務(wù)。

          2. workLoop對隊列里取第一個任務(wù)currentTask,進(jìn)入循環(huán)開始執(zhí)行。

            • 如果任務(wù)執(zhí)行完后,還有連續(xù)的回調(diào),則 currentTask.callback = continuationCallback
            • 否則移除已完成的任務(wù)
            • 當(dāng)該任務(wù)沒有時間 或 需要中斷 (渲染任務(wù) 或 其他高優(yōu)任務(wù)插入等),則讓出主線程。

            • 否則執(zhí)行任務(wù) currentTask.callback()

            • 更新任務(wù)currentTask,繼續(xù)循環(huán)走起。

          這里還涉及更多細(xì)節(jié),例如:

          • requestAnimationFrame 計算一幀的空余時間;
          • 使用new MessageChannel () 執(zhí)行宏任務(wù);
          • 優(yōu)先級;
          • ...

          這里不做詳細(xì)說明。

          8.小總結(jié)

          • 我們想要實現(xiàn)并發(fā)訴求,就需要從底層重構(gòu),即FiberNode的實現(xiàn)。
          • 調(diào)用棧call stack是無法做到并發(fā) (異步可中斷) 訴求,故React自行實現(xiàn)了一套虛擬棧幀。
          • 虛擬棧幀 是要具備調(diào)度能力的,也就是如何在適當(dāng)?shù)臅r候去執(zhí)行任務(wù)。
          • scheduler 可做到異步可中斷,并可自主分配優(yōu)先級高低的任務(wù)。

          (即任務(wù) (狀態(tài): 運(yùn)行/中斷/繼續(xù)) Lane運(yùn)行策略)

          (實際上,scheduler + Lane 調(diào)度策略遠(yuǎn)比該處理復(fù)雜的多??)

          圖: 前后對比 (個人理解, 錯誤請指正)

          以上,同學(xué)們是不是對React Fiber架構(gòu)有了初步的理解哦~

          其他說明

          雙緩存機(jī)制

          參考: 雙緩存Fiber樹[22]


          至多有兩棵 Fiber Tree。

          分別叫做current fiber tree 和 workInProgress fiber tree。

          即在屏幕上已建立的fiber tree 和 因為數(shù)據(jù)變化重新在內(nèi)存里創(chuàng)建的fiber tree。

          他們之間是通過 alternate屬性(指針) 建立連接。


          簡單的說:

          1. 就是workInProgress fiber的創(chuàng)建 是否可復(fù)用 current fiber的節(jié)點(diǎn)。后續(xù)可再詳看diff算法。
          2. workInProgress fiber tree 將確定要變更節(jié)點(diǎn),渲染到屏幕上。
          3. workInProgress fiber tree 晉升為 current fiber tree。


          參考資料

          [1]

          從內(nèi)部了解現(xiàn)代瀏覽器(3): https://juejin.cn/post/6844903687383416840

          [2]

          渲染樹構(gòu)建、布局及繪制: https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction

          [3]

          RequestIdleCallback 實驗: https://github.com/Linjiayu6/FE-RequestIdleCallback-demo

          [4]

          RequestIdleCallback: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback

          [5]

          RequestIdleCallback: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback

          [6]

          RequestIdleCallback 實驗: https://github.com/Linjiayu6/FE-RequestIdleCallback-demo

          [7]

          React: stack reconciler實現(xiàn): https://zh-hans.reactjs.org/docs/implementation-notes.html

          [8]

          React 算法之深度優(yōu)先遍歷: https://juejin.cn/post/6912280245055782920

          [9]

          調(diào)用棧: https://segmentfault.com/a/1190000010360316

          [10]

          Leetcode: 斐波拉契數(shù)列: https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/

          [11]

          Leetcode: 70. 爬樓梯: https://leetcode-cn.com/problems/climbing-stairs/

          [12]

          ReactFiberWorkLoop.old.js: https://github.com/facebook/react/blob/v17.0.1/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1558

          [13]

          program: https://en.wikipedia.org/wiki/Computer_program

          [14]

          algorithm: https://en.wikipedia.org/wiki/Algorithm

          [15]

          problem: https://en.wikipedia.org/wiki/Problem_solving

          [16]

          partial order: https://en.wikipedia.org/wiki/Partial_Order

          [17]

          Call Stack: https://segmentfault.com/a/1190000021456103

          [18]

          FiberNode.js: https://github.com/facebook/react/blob/1fb18e22ae66fdb1dc127347e169e73948778e5a/packages/react-reconciler/src/ReactFiber.new.js#L117

          [19]

          beginWork(): https://github.com/facebook/react/blob/970fa122d8188bafa600e9b5214833487fbf1092/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L3058

          [20]

          completeWork(): https://github.com/facebook/react/blob/970fa122d8188bafa600e9b5214833487fbf1092/packages/react-reconciler/src/ReactFiberCompleteWork.new.js#L652

          [21]

          workloop.js: https://github.com/facebook/react/blob/v17.0.1/packages/scheduler/src/Scheduler.js#L164

          [22]

          雙緩存Fiber樹: https://react.iamkasong.com/process/doubleBuffer.html#update%E6%97%B6

          ?? 謝謝支持

          以上便是本次分享的全部內(nèi)容,希望對你有所幫助^_^

          喜歡的話別忘了 分享、點(diǎn)贊、收藏 三連哦~。

          歡迎關(guān)注公眾號 前端Sharing 收貨大廠一手好文章~


          瀏覽 49
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  日皮太爽了我要看免费视频 | 日韩理论视频 | 五月天av伊人av | 偷拍亚洲天堂 | 日韩一级无码免费视频 |