<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)淺析

          共 12942字,需瀏覽 26分鐘

           ·

          2021-10-13 16:59

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

          1.瀏覽器渲染

          為了更好的理解 React Fiber, 我們先簡單了解下渲染器進程的內(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): 當某一幀時長高于平均幀時長。

          • 一般來說瀏覽器刷新率在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, 當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進程刷新這一幀。

          4.?一幀結(jié)束。

          1.3 丟幀實驗

          怎么就丟幀了呢?

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

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

          Github:?RequestIdleCallback 實驗[3]

          當用戶點擊任一按鍵 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ù)

          ???unit:?10000,

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

          ???onOneUnit:?function?()?{??for?(var?i?=?0;?i?<=?500000;?i++)?{}?},

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

          ???onSyncUnit:?function?()?{

          ??????let?_u?=?0

          ??????while?(_u?
          ?????????Work.onOneUnit()

          ?????????_u?++

          ??????}

          ????}

          ?}

          1.4 解決丟幀

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

          在連續(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ù)一般會按先進先調(diào)用的順序執(zhí)行,然而,如果回調(diào)函數(shù)指定了執(zhí)行超時時間timeout,則有可能為了在超時前執(zhí)行函數(shù)而打亂執(zhí)行順序。

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

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

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

          Github: RequestIdleCallback 實驗

          const?bindClick?=?id?=>?

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

          //?綁定click事件

          bindClick('btnA')

          bindClick('btnB')

          bindClick('btnC')



          var?Work?=?{

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

          ????unit:?10000,

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

          ????onOneUnit:?function?()?{??for?(var?i?=?0;?i?<=?500000;?i++)?{}?},

          ????//?異步處理

          ????onAsyncUnit:?function?()?{

          ????????//?空閑時間?1ms

          ????????const?FREE_TIME?=?1

          ????????let?_u?=?0



          ????????function?cb(deadline)?{

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

          ????????????while?(_u??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)缺點

          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 流程和代碼解析

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

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

          某React節(jié)點如下:

          ??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é)點?開始工作:?return?workInProgress.child;?返回的是該節(jié)點的孩子

          ??let?next?=?beginWork(...);



          ??if?(next?===?null)?{

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

          ????completeUnitOfWork(unitOfWork);

          ??}?else?{

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

          ????workInProgress?=?next;

          ??}

          }



          /**

          ?*?siblingFiber:?兄弟節(jié)點

          ?*?returnFiber:?父親節(jié)點

          ?*/


          function?completeUnitOfWork(unitOfWork:?Fiber):?void?{

          ??let?completedWork?=?unitOfWork;



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

          ??do?{

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



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

          ????if?(completedWork.sibling?!==?null)?{

          ??????workInProgress?=?siblingFiber;

          ??????return;

          ????}



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

          ????completedWork?=?completedWork.return;

          ????workInProgress?=?completedWork;

          ??}?while?(completedWork?!==?null);



          ??//?最后,?是root節(jié)點,?結(jié)束

          ??if?(workInProgressRootExitStatus?===?RootIncomplete)?{

          ????workInProgressRootExitStatus?=?RootCompleted;

          ??}

          }

          3.上述總結(jié)

          因果關(guān)系

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

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

          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進入執(zhí)行區(qū)域。

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

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

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

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

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

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

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

          描述:

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

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

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

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

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

          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ù),等待時機成熟后,再去安排執(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í)行順序的,秉承著先進后出原則,是如何做到某任務(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">123div>

          <script?type="text/babel">

          ????const?App?=?()?=>?{

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



          ????????return?(

          ????????????<div?id="app?1">

          ????????????????<h1?id="2-1?h1">標題?h1h1>


          ????????????????<ul?id="2-2?ul">?

          ????????????????????<li?id="3-1?li"?onClick={()?=>?onSetSum(d?=>?d?+?1)}>點擊?h2li>


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

          ????????????????ul>



          ????????????????<h3?id="2-3?h3">標題?h3h3>

          ????????????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]

          虛線: 表達構(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?(即示例中?"linjiayu">)



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

          3.?創(chuàng)建fiberNode?App?

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

          ???

          4.?創(chuàng)建fiberNode?div?

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

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

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

          6.3 工作屬性

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

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

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

          7.Reconciler 和 Scheduler

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

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

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

          7.1 Reconciler 運行流程淺析

          1. 【輸入】??當數(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?
          ????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.?輸出樹?(可看下雙緩存機制)

          ??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.?輸出樹?(可看下雙緩存機制)

          ???finishConcurrentRender(root,?exitStatus,?lanes);

          ???

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

          ???ensureRootIsScheduled(root,?now());

          ????if?(root.callbackNode?===?originalCallbackNode)?{

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

          ??????return?performConcurrentWorkOnRoot.bind(null,?root);

          ????}



          ????return?null;

          }
          1. 輸出

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

          1. 小總結(jié)

          7.2 Scheduler 運行流程淺析

          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)交給主進程

          ????shouldYieldToHost?()?{

          ????????//?沒有剩余時間

          ????????if?(currentTime?>=?deadline)?{

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

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

          ????????????????return?true;?//?中斷

          ????????????}

          ????????????//?是否超過?300ms

          ????????????return?currentTime?>=?maxYieldInterval;

          ????????}


          ????????//?還有剩余時間

          ????????return?false;

          ????},


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

          ????workLoop?()?{

          ????????//?當前第一個任務(wù)

          ????????currentTask?=?taskQueue[0];

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

          ????????while(currentTask?!==?null)?{

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

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

          ????????????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')?{

          ????????????????//?則保留當前

          ????????????????currentTask.callback?=?continuationCallback;

          ????????????}?else??{

          ????????????????//?將currentTask移除該隊列

          ????????????????pop(taskQueue);

          ????????????}


          ????????????//?更新currentTask

          ????????????currentTask?=?peek(taskQueue);

          ????????}

          ????},

          }

          簡而言之:

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

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

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

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

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

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

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

          這里不做詳細說明。

          8.小總結(jié)

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

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

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

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

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

          其他說明

          雙緩存機制

          參考:?雙緩存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é)點。后續(xù)可再詳看diff算法。
          2. workInProgress fiber tree 將確定要變更節(jié)點,渲染到屏幕上。
          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)容,希望對你有所幫助^_^

          喜歡的話別忘了?分享、點贊、收藏?三連哦~。

          歡迎關(guān)注公眾號?ELab團隊?收貨大廠一手好文章~

          我們來自字節(jié)跳動,是旗下大力教育前端部門,負責(zé)字節(jié)跳動教育全線產(chǎn)品前端開發(fā)工作。

          我們圍繞產(chǎn)品品質(zhì)提升、開發(fā)效率、創(chuàng)意與前沿技術(shù)等方向沉淀與傳播專業(yè)知識及案例,為業(yè)界貢獻經(jīng)驗價值。包括但不限于性能監(jiān)控、組件庫、多端技術(shù)、Serverless、可視化搭建、音視頻、人工智能、產(chǎn)品設(shè)計與營銷等內(nèi)容。

          歡迎感興趣的同學(xué)在評論區(qū)或使用內(nèi)推碼內(nèi)推到作者部門拍磚哦 ??

          字節(jié)跳動校/社招內(nèi)推碼:?W7HD8A6

          投遞鏈接: https://jobs.toutiao.com/s/dLLnGv


          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产人妻在线观看 | 毛片日韩 | 91在线无码精品秘 入口九色1 | 日日操夜夜爽精品a级 | 黄色欧美精品 |