<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

          共 20351字,需瀏覽 41分鐘

           ·

          2021-08-03 08:12

          點擊上方“前端Sharing”,選擇“設為星標
          第一時間關注技術干貨!



          作者:獨釣寒江雪

          原文:https://segmentfault.com/a/1190000039682751


          正文

          React Fiber 是Facebook花費兩年余時間對 React 做出的一個重大改變與優(yōu)化,是對 React 核心算法的一次重新實現(xiàn)。從Facebook在 React Conf 2017會議上確認,React Fiber 會在React 16 版本發(fā)布至今,也已過去三年有余,如今,React 17 業(yè)已發(fā)布,社區(qū)關于Fiber的優(yōu)秀文章不在少數(shù)。

          本文源于一次團隊內部的技術分享,借鑒社區(qū)優(yōu)秀文章,結合個人理解,進行整合,從六個問題出發(fā),對 React Fiber 進行理解與認識,同時對時下熱門的前端框架Svelte進行簡要介紹與剖析,希望對正在探究 React 及各前端框架的小伙伴們能有所助益。

          全文大量參考和引用以下幾篇博文,讀者可自行查閱:

          • React技術揭秘[1]
          • 前端工程師的自我修養(yǎng):React Fiber 是如何實現(xiàn)更新過程可控的[2]
          • 新興前端框架 Svelte 從入門到原理
          • 以 React 為例,說說框架和性能(下)[3]

          一、React 的設計理念是什么?

          React官網在React哲學[4]一節(jié)開篇提到:

          我們認為,React 是用 JavaScript 構建快速響應的大型 Web 應用程序的首選方式。它在 Facebook 和 Instagram 上表現(xiàn)優(yōu)秀。React 最棒的部分之一是引導我們思考如何構建一個應用。

          由此可見,React 追求的是 “快速響應”,那么,“快速響應“的制約因素都有什么呢?

          • CPU的瓶頸:當項目變得龐大、組件數(shù)量繁多、遇到大計算量的操作或者設備性能不足使得頁面掉幀,導致卡頓。
          • IO的瓶頸:發(fā)送網絡請求后,由于需要等待數(shù)據(jù)返回才能進一步操作導致不能快速響應。

          本文要聊的fiber 架構主要就是用來解決 CPU 和網絡的問題,這兩個問題一直也是最影響前端開發(fā)體驗的地方,一個會造成卡頓,一個會造成白屏。為此 react 為前端引入了兩個新概念:Time Slicing 時間分片和Suspense。

          二、React的“先天不足” —— 聽說 Vue 3.0 采用了動靜結合的 Dom diff,React 為何不跟進?

          Vue 3.0 動靜結合的 Dom diff

          Vue3.0 提出動靜結合的 DOM diff 思想,動靜結合的 DOM diff其實是在預編譯階段進行了優(yōu)化。之所以能夠做到預編譯優(yōu)化,是因為 Vue core 可以靜態(tài)分析 template,在解析模版時,整個 parse 的過程是利用正則表達式順序解析模板,當解析到開始標簽、閉合標簽和文本的時候都會分別執(zhí)行對應的回調函數(shù),來達到構造 AST 樹的目的。

          借助預編譯過程,Vue 可以做到的預編譯優(yōu)化就很強大了。比如在預編譯時標記出模版中可能變化的組件節(jié)點,再次進行渲染前 diff 時就可以跳過“永遠不會變化的節(jié)點”,而只需要對比“可能會變化的動態(tài)節(jié)點”。這也就是動靜結合的 DOM diff 將 diff 成本與模版大小正相關優(yōu)化到與動態(tài)節(jié)點正相關的理論依據(jù)。

          React 能否像 Vue 那樣進行預編譯優(yōu)化?

          Vue 需要做數(shù)據(jù)雙向綁定,需要進行數(shù)據(jù)攔截或代理,那它就需要在預編譯階段靜態(tài)分析模版,分析出視圖依賴了哪些數(shù)據(jù),進行響應式處理。而 React 就是局部重新渲染,React 拿到的或者說掌管的,所負責的就是一堆遞歸 React.createElement 的執(zhí)行調用(參考下方經過Babel轉換的代碼),它無法從模版層面進行靜態(tài)分析。JSX 和手寫的 render function[5] 是完全動態(tài)的,過度的靈活性導致運行時可以用于優(yōu)化的信息不足。

          JSX 寫法:

          <div>
            <h1>六個問題助你理解 React Fiber</h1>
            <ul>
              <li>React</li>
              <li>Vue</li>
            </ul>
          </div>

          遞歸 React.createElement:

          // Babel轉換后
          React.createElement(
            "div",
            null,
           React.createElement(
              "h1",
              null,
              "\u516D\u4E2A\u95EE\u9898\u52A9\u4F60\u7406\u89E3 React Fiber"
           ),
           React.createElement(
              "ul",
              null,
             React.createElement("li"null"React"),
             React.createElement("li"null"Vue")
           )
          );

          JSX vs Template

          jsx and Templates
          • JSX 具有 JavaScript 的完整表現(xiàn)力,可以構建非常復雜的組件。但是靈活的語法,也意味著引擎難以理解,無法預判開發(fā)者的用戶意圖,從而難以優(yōu)化性能。
          • Template 模板是一種非常有約束的語言,你只能以某種方式去編寫模板。

          既然存在以上編譯時先天不足,在運行時優(yōu)化方面,React一直在努力。比如,React15實現(xiàn)了batchedUpdates(批量更新)。即同一事件回調函數(shù)上下文中的多次setState只會觸發(fā)一次更新。

          但是,如果單次更新就很耗時,頁面還是會卡頓(這在一個維護時間很長的大應用中是很常見的)。這是因為React15的更新流程是同步執(zhí)行的,一旦開始更新直到頁面渲染前都不能中斷。

          資料參考:以 React 為例,說說框架和性能(下)[6] | 新興前端框架 Svelte 從入門到原理

          三、從架構演變看不斷進擊的 React 都做過哪些優(yōu)化?

          React渲染頁面的兩個階段

          • 調度階段(reconciliation):在這個階段 React 會更新數(shù)據(jù)生成新的 Virtual DOM,然后通過Diff算法,快速找出需要更新的元素,放到更新隊列中去,得到新的更新隊列。
          • 渲染階段(commit):這個階段 React 會遍歷更新隊列,將其所有的變更一次性更新到DOM上。

          React 15 架構

          React15架構可以分為兩層:

          • Reconciler(協(xié)調器)—— 負責找出變化的組件;
          • Renderer(渲染器)—— 負責將變化的組件渲染到頁面上;

          在React15及以前,Reconciler采用遞歸的方式創(chuàng)建虛擬DOM,遞歸過程是不能中斷的。如果組件樹的層級很深,遞歸會占用線程很多時間,遞歸更新時間超過了16ms,用戶交互就會卡頓。

          為了解決這個問題,React16將遞歸的無法中斷的更新重構為異步的可中斷更新,由于曾經用于遞歸的虛擬DOM數(shù)據(jù)結構已經無法滿足需要。于是,全新的Fiber架構應運而生。

          React 16 架構

          為了解決同步更新長時間占用線程導致頁面卡頓的問題,也為了探索運行時優(yōu)化的更多可能,React開始重構并一直持續(xù)至今。重構的目標是實現(xiàn)Concurrent Mode(并發(fā)模式)。

          從v15到v16,React團隊花了兩年時間將源碼架構中的Stack Reconciler重構為Fiber Reconciler。

          React16架構可以分為三層:

          • Scheduler(調度器)—— 調度任務的優(yōu)先級,高優(yōu)任務優(yōu)先進入Reconciler;
          • Reconciler(協(xié)調器)—— 負責找出變化的組件:更新工作從遞歸變成了可以中斷的循環(huán)過程。Reconciler內部采用了Fiber的架構;
          • Renderer(渲染器)—— 負責將變化的組件渲染到頁面上。

          React 17 優(yōu)化

          React16的expirationTimes模型只能區(qū)分是否>=expirationTimes決定節(jié)點是否更新。React17的lanes模型可以選定一個更新區(qū)間,并且動態(tài)的向區(qū)間中增減優(yōu)先級,可以處理更細粒度的更新。

          Lane用二進制位表示任務的優(yōu)先級,方便優(yōu)先級的計算(位運算),不同優(yōu)先級占用不同位置的“賽道”,而且存在批的概念,優(yōu)先級越低,“賽道”越多。高優(yōu)先級打斷低優(yōu)先級,新建的任務需要賦予什么優(yōu)先級等問題都是Lane所要解決的問題。

          Concurrent Mode的目的是實現(xiàn)一套可中斷/恢復的更新機制。其由兩部分組成:

          • 一套協(xié)程架構:Fiber Reconciler
          • 基于協(xié)程架構的啟發(fā)式更新算法:控制協(xié)程架構工作方式的算法

          資料參考:React17新特性:啟發(fā)式更新算法[7]

          四、瀏覽器一幀都會干些什么以及requestIdleCallback的啟示

          瀏覽器一幀都會干些什么?

          我們都知道,頁面的內容都是一幀一幀繪制出來的,瀏覽器刷新率代表瀏覽器一秒繪制多少幀。原則上說 1s 內繪制的幀數(shù)也多,畫面表現(xiàn)就也細膩。目前瀏覽器大多是 60Hz(60幀/s),每一幀耗時也就是在 16.6ms 左右。那么在這一幀的(16.6ms) 過程中瀏覽器又干了些什么呢?

          瀏覽器一幀都會干些什么

          通過上面這張圖可以清楚的知道,瀏覽器一幀會經過下面這幾個過程:

          1. 接受輸入事件
          2. 執(zhí)行事件回調
          3. 開始一幀
          4. 執(zhí)行 RAF (RequestAnimationFrame)
          5. 頁面布局,樣式計算
          6. 繪制渲染
          7. 執(zhí)行 RIC (RequestIdelCallback)

          第七步的 RIC 事件不是每一幀結束都會執(zhí)行,只有在一幀的 16.6ms 中做完了前面 6 件事兒且還有剩余時間,才會執(zhí)行。如果一幀執(zhí)行結束后還有時間執(zhí)行 RIC 事件,那么下一幀需要在事件執(zhí)行結束才能繼續(xù)渲染,所以 RIC 執(zhí)行不要超過 30ms,如果長時間不將控制權交還給瀏覽器,會影響下一幀的渲染,導致頁面出現(xiàn)卡頓和事件響應不及時。

          requestIdleCallback 的啟示

          我們以瀏覽器是否有剩余時間作為任務中斷的標準,那么我們需要一種機制,當瀏覽器有剩余時間時通知我們。

          requestIdleCallback((deadline) => {
          // deadline 有兩個參數(shù)
            // timeRemaining(): 當前幀還剩下多少時間
            // didTimeout: 是否超時
          // 另外 requestIdleCallback 后如果跟上第二個參數(shù) {timeout: ...} 則會強制瀏覽器在當前幀執(zhí)行完后執(zhí)行。
           if (deadline.timeRemaining() > 0) {
             // TODO
           } else {
            requestIdleCallback(otherTasks);
           }
          });
          // 用法示例
          var tasksNum = 10000

          requestIdleCallback(unImportWork)

          function unImportWork(deadline{
            while (deadline.timeRemaining() && tasksNum > 0) {
              console.log(`執(zhí)行了 ${10000 - tasksNum + 1}個任務`)
              tasksNum--
            }
            if (tasksNum > 0) { // 在未來的幀中繼續(xù)執(zhí)行
              requestIdleCallback(unImportWork)
            }
          }

          其實部分瀏覽器已經實現(xiàn)了這個API,這就是requestIdleCallback。但是由于以下因素,F(xiàn)acebook 拋棄了 requestIdleCallback 的原生 API:

          • 瀏覽器兼容性;
          • 觸發(fā)頻率不穩(wěn)定,受很多因素影響。比如當我們的瀏覽器切換tab后,之前tab注冊的requestIdleCallback觸發(fā)的頻率會變得很低。

          參考:requestIdleCallback 的 FPS 只有 20[8]

          基于以上原因,在React中實現(xiàn)了功能更完備的requestIdleCallbackpolyfill,這就是Scheduler。除了在空閑時觸發(fā)回調的功能外,Scheduler還提供了多種調度優(yōu)先級供任務設置。

          資料參考:requestIdleCallback-后臺任務調度[9]

          五、 Fiber 為什么是 React 性能的一個飛躍?

          什么是 Fiber

          Fiber 的英文含義是“纖維”,它是比線程(Thread)更細的線,比線程(Thread)控制得更精密的執(zhí)行模型。在廣義計算機科學概念中,F(xiàn)iber 又是一種協(xié)作的(Cooperative)編程模型(協(xié)程),幫助開發(fā)者用一種【既模塊化又協(xié)作化】的方式來編排代碼。

          在 React 中,Fiber 就是 React 16 實現(xiàn)的一套新的更新機制,讓 React 的更新過程變得可控,避免了之前采用遞歸需要一氣呵成影響性能的做法。

          React Fiber 中的時間分片

          把一個耗時長的任務分成很多小片,每一個小片的運行時間很短,雖然總時間依然很長,但是在每個小片執(zhí)行完之后,都給其他任務一個執(zhí)行的機會,這樣唯一的線程就不會被獨占,其他任務依然有運行的機會。

          React Fiber 把更新過程碎片化,每執(zhí)行完一段更新過程,就把控制權交還給 React 負責任務協(xié)調的模塊,看看有沒有其他緊急任務要做,如果沒有就繼續(xù)去更新,如果有緊急任務,那就去做緊急任務。

          Stack Reconciler

          基于棧的 Reconciler,瀏覽器引擎會從執(zhí)行棧的頂端開始執(zhí)行,執(zhí)行完畢就彈出當前執(zhí)行上下文,開始執(zhí)行下一個函數(shù),直到執(zhí)行棧被清空才會停止。然后將執(zhí)行權交還給瀏覽器。由于 React 將頁面視圖視作一個個函數(shù)執(zhí)行的結果。每一個頁面往往由多個視圖組成,這就意味著多個函數(shù)的調用。

          如果一個頁面足夠復雜,形成的函數(shù)調用棧就會很深。每一次更新,執(zhí)行棧需要一次性執(zhí)行完成,中途不能干其他的事兒,只能"一心一意"。結合前面提到的瀏覽器刷新率,JS 一直執(zhí)行,瀏覽器得不到控制權,就不能及時開始下一幀的繪制。如果這個時間超過 16ms,當頁面有動畫效果需求時,動畫因為瀏覽器不能及時繪制下一幀,這時動畫就會出現(xiàn)卡頓。不僅如此,因為事件響應代碼是在每一幀開始的時候執(zhí)行,如果不能及時繪制下一幀,事件響應也會延遲。

          Fiber Reconciler

          鏈表結構

          在 React Fiber 中用鏈表遍歷的方式替代了 React 16 之前的棧遞歸方案。在 React 16 中使用了大量的鏈表。

          • 使用多向鏈表的形式替代了原來的樹結構;
            <div id="A">
            A1
            <div id="B1">
              B1
              <div id="C1"></div>
            </div>
            <div id="B2">
              B2
            </div>
            </div>
          多向鏈表
          • 副作用單鏈表;
          副作用單鏈表
          • 狀態(tài)更新單鏈表;
          狀態(tài)更新單鏈表
          • ...

          鏈表是一種簡單高效的數(shù)據(jù)結構,它在當前節(jié)點中保存著指向下一個節(jié)點的指針;遍歷的時候,通過操作指針找到下一個元素。

          鏈表

          鏈表相比順序結構數(shù)據(jù)格式的好處就是:

          1. 操作更高效,比如順序調整、刪除,只需要改變節(jié)點的指針指向就好了。
          2. 不僅可以根據(jù)當前節(jié)點找到下一個節(jié)點,在多向鏈表中,還可以找到他的父節(jié)點或者兄弟節(jié)點。

          但鏈表也不是完美的,缺點就是:

          1. 比順序結構數(shù)據(jù)更占用空間,因為每個節(jié)點對象還保存有指向下一個對象的指針。
          2. 不能自由讀取,必須找到他的上一個節(jié)點。

          React 用空間換時間,更高效的操作可以方便根據(jù)優(yōu)先級進行操作。同時可以根據(jù)當前節(jié)點找到其他節(jié)點,在下面提到的掛起和恢復過程中起到了關鍵作用。

          斐波那契數(shù)列的 Fiber

          遞歸形式的斐波那契數(shù)列寫法:

          function fib(n{
            if (n <= 2) {
              return 1;
            } else {
              return fib(n - 1) + fib(n - 2);
            }
          }

          采用 Fiber 的思路將其改寫為循環(huán)(這個例子并不能和 React Fiber 的對等):

          function fib(n{
            let fiber = { arg: n, returnAddrnulla0 }, consoled = false;
            // 標記循環(huán)
            rec: while (true) {
              // 當展開完全后,開始計算
              if (fiber.arg <= 2) {
                let sum = 1;
                // 尋找父級
                while (fiber.returnAddr) {
                  if(!consoled) {
                    // 在這里打印查看形成的鏈表形式的 fiber 對象
                    consoled=true
                    console.log(fiber)
                  }
                  fiber = fiber.returnAddr;
                  if (fiber.a === 0) {
                    fiber.a = sum;
                    fiber = { arg: fiber.arg - 2returnAddr: fiber, a0 };
                    continue rec;
                  }
                  sum += fiber.a;
                }
                return sum;
              } else {
                // 先展開
                fiber = { arg: fiber.arg - 1returnAddr: fiber, a0 };
              }
            }
          }

          六、React Fiber 是如何實現(xiàn)更新過程可控?

          更新過程的可控主要體現(xiàn)在下面幾個方面:

          • 任務拆分
          • 任務掛起、恢復、終止
          • 任務具備優(yōu)先級

          任務拆分

          在 React Fiber 機制中,它采用"化整為零"的思想,將調和階段(Reconciler)遞歸遍歷 VDOM 這個大任務分成若干小任務,每個任務只負責一個節(jié)點的處理。

          任務掛起、恢復、終止

          workInProgress tree

          workInProgress 代表當前正在執(zhí)行更新的 Fiber 樹。在 render 或者 setState 后,會構建一顆 Fiber 樹,也就是 workInProgress tree,這棵樹在構建每一個節(jié)點的時候會收集當前節(jié)點的副作用,整棵樹構建完成后,會形成一條完整的副作用鏈。

          currentFiber tree

          currentFiber 表示上次渲染構建的 Filber 樹。在每一次更新完成后 workInProgress 會賦值給 currentFiber。在新一輪更新時 workInProgress tree 再重新構建,新 workInProgress 的節(jié)點通過 alternate 屬性和 currentFiber 的節(jié)點建立聯(lián)系。

          在新 workInProgress tree 的創(chuàng)建過程中,會同 currentFiber 的對應節(jié)點進行 Diff 比較,收集副作用。同時也會復用和 currentFiber 對應的節(jié)點對象,減少新創(chuàng)建對象帶來的開銷。也就是說無論是創(chuàng)建還是更新、掛起、恢復以及終止操作都是發(fā)生在 workInProgress tree 創(chuàng)建過程中的。workInProgress tree 構建過程其實就是循環(huán)的執(zhí)行任務和創(chuàng)建下一個任務。

          掛起

          當?shù)谝粋€小任務完成后,先判斷這一幀是否還有空閑時間,沒有就掛起下一個任務的執(zhí)行,記住當前掛起的節(jié)點,讓出控制權給瀏覽器執(zhí)行更高優(yōu)先級的任務。

          恢復

          在瀏覽器渲染完一幀后,判斷當前幀是否有剩余時間,如果有就恢復執(zhí)行之前掛起的任務。如果沒有任務需要處理,代表調和階段完成,可以開始進入渲染階段。

          1. 如何判斷一幀是否有空閑時間的呢?

          使用前面提到的 RIC (RequestIdleCallback) 瀏覽器原生 API,React 源碼中為了兼容低版本的瀏覽器,對該方法進行了 Polyfill。

          1. 恢復執(zhí)行的時候又是如何知道下一個任務是什么呢?

          答案是在前面提到的鏈表。在 React Fiber 中每個任務其實就是在處理一個 FiberNode 對象,然后又生成下一個任務需要處理的 FiberNode。

          終止

          其實并不是每次更新都會走到提交階段。當在調和過程中觸發(fā)了新的更新,在執(zhí)行下一個任務的時候,判斷是否有優(yōu)先級更高的執(zhí)行任務,如果有就終止原來將要執(zhí)行的任務,開始新的 workInProgressFiber 樹構建過程,開始新的更新流程。這樣可以避免重復更新操作。這也是在 React 16 以后生命周期函數(shù) componentWillMount 有可能會執(zhí)行多次的原因。

          workInProgress tree 構建

          任務具備優(yōu)先級

          React Fiber 除了通過掛起,恢復和終止來控制更新外,還給每個任務分配了優(yōu)先級。具體點就是在創(chuàng)建或者更新 FiberNode 的時候,通過算法給每個任務分配一個到期時間(expirationTime)。在每個任務執(zhí)行的時候除了判斷剩余時間,如果當前處理節(jié)點已經過期,那么無論現(xiàn)在是否有空閑時間都必須執(zhí)行該任務。過期時間的大小還代表著任務的優(yōu)先級。

          任務在執(zhí)行過程中順便收集了每個 FiberNode 的副作用,將有副作用的節(jié)點通過 firstEffect、lastEffect、nextEffect 形成一條副作用單鏈表 A1(TEXT)-B1(TEXT)-C1(TEXT)-C1-C2(TEXT)-C2-B1-B2(TEXT)-B2-A。

          其實最終都是為了收集到這條副作用鏈表,有了它,在接下來的渲染階段就通過遍歷副作用鏈完成 DOM 更新。這里需要注意,更新真實 DOM 的這個動作是一氣呵成的,不能中斷,不然會造成視覺上的不連貫(commit)。

          <div id="A1">
            A1
            <div id="B1">
              B1
              <div id="C1">C1</div>
              <div id="C2">C2</div>
            </div>
            <div id="B2">
              B2
            </div>
          </div>
          副作用鏈

          直觀展示

          正是基于以上這些過程,使用Fiber,我們就有了在社區(qū)經常看到的兩張對比圖[10]

          清晰展示及交互、源碼可通過下面兩個鏈接進入,查看網頁源代碼。

          • Stack Example[11]
          • Fiber Example[12]
          為了方便大家對比,我就直接放上兩張對比圖吧,大家自行比對,差別還是很明顯的



          Fiber 結構長什么樣?

          基于時間分片的增量更新需要更多的上下文信息,之前的vDOM tree顯然難以滿足,所以擴展出了fiber tree(即Fiber上下文的vDOM tree),更新過程就是根據(jù)輸入數(shù)據(jù)以及現(xiàn)有的fiber tree構造出新的fiber tree(workInProgress tree)。

          FiberNode 上的屬性有很多,根據(jù)筆者的理解,以下這么幾個屬性是值得關注的:return、child、sibling(主要負責fiber鏈表的鏈接);stateNode;effectTag;expirationTime;alternate;nextEffect。各屬性介紹參看下面的class FiberNode

          class FiberNode {
            constructor(tag, pendingProps, key, mode) {
              // 實例屬性
              this.tag = tag; // 標記不同組件類型,如函數(shù)組件、類組件、文本、原生組件...
              this.key = key; // react 元素上的 key 就是 jsx 上寫的那個 key ,也就是最終 ReactElement 上的
              this.elementType = null// createElement的第一個參數(shù),ReactElement 上的 type
              this.type = null// 表示fiber的真實類型 ,elementType 基本一樣,在使用了懶加載之類的功能時可能會不一樣
              this.stateNode = null// 實例對象,比如 class 組件 new 完后就掛載在這個屬性上面,如果是RootFiber,那么它上面掛的是 FiberRoot,如果是原生節(jié)點就是 dom 對象
              // fiber
              this.return = null// 父節(jié)點,指向上一個 fiber
              this.child = null// 子節(jié)點,指向自身下面的第一個 fiber
              this.sibling = null// 兄弟組件, 指向一個兄弟節(jié)點
              this.index = 0//  一般如果沒有兄弟節(jié)點的話是0 當某個父節(jié)點下的子節(jié)點是數(shù)組類型的時候會給每個子節(jié)點一個 index,index 和 key 要一起做 diff
              this.ref = null// reactElement 上的 ref 屬性
              this.pendingProps = pendingProps; // 新的 props
              this.memoizedProps = null// 舊的 props
              this.updateQueue = null// fiber 上的更新隊列執(zhí)行一次 setState 就會往這個屬性上掛一個新的更新, 每條更新最終會形成一個鏈表結構,最后做批量更新
              this.memoizedState = null// 對應  memoizedProps,上次渲染的 state,相當于當前的 state,理解成 prev 和 next 的關系
              this.mode = mode; // 表示當前組件下的子組件的渲染方式
              // effects
              this.effectTag = NoEffect; // 表示當前 fiber 要進行何種更新(更新、刪除等)
              this.nextEffect = null// 指向下個需要更新的fiber
              this.firstEffect = null// 指向所有子節(jié)點里,需要更新的 fiber 里的第一個
              this.lastEffect = null// 指向所有子節(jié)點中需要更新的 fiber 的最后一個
              this.expirationTime = NoWork; // 過期時間,代表任務在未來的哪個時間點應該被完成
              this.childExpirationTime = NoWork; // child 過期時間
              this.alternate = null// current 樹和 workInprogress 樹之間的相互引用
            }
          }
          fiber-tree

          圖片來源:完全理解React Fiber[13]

          function performUnitWork(currentFiber){
              //beginWork(currentFiber) //找到兒子,并通過鏈表的方式掛到currentFiber上,每一偶兒子就找后面那個兄弟
            //有兒子就返回兒子
            if(currentFiber.child){
              return currentFiber.child;
            } 
            //如果沒有兒子,則找弟弟
            while(currentFiber){//一直往上找
              //completeUnitWork(currentFiber);//將自己的副作用掛到父節(jié)點去
              if(currentFiber.sibling){
                return currentFiber.sibling
              }
              currentFiber = currentFiber.return;
            }
          }

          Concurrent Mode (并發(fā)模式)

          Concurrent Mode 指的就是 React 利用上面 Fiber 帶來的新特性的開啟的新模式 (mode)。react17開始支持concurrent mode,這種模式的根本目的是為了讓應用保持cpu和io的快速響應,它是一組新功能,包括Fiber、Scheduler、Lane,可以根據(jù)用戶硬件性能和網絡狀況調整應用的響應速度,核心就是為了實現(xiàn)異步可中斷的更新。concurrent mode也是未來react主要迭代的方向。

          目前 React 實驗版本允許用戶選擇三種 mode:

          1. Legacy Mode: 就相當于目前穩(wěn)定版的模式
          2. Blocking Mode: 應該是以后會代替 Legacy Mode 而長期存在的模式
          3. Concurrent Mode: 以后會變成 default 的模式

          Concurrent Mode 其實開啟了一堆新特性,其中有兩個最重要的特性可以用來解決我們開頭提到的兩個問題:

          1. Suspense[14]:Suspense 是 React 提供的一種異步處理的機制, 它不是一個具體的數(shù)據(jù)請求庫。它是React 提供的原生的組件異步調用原語。
          2. useTrasition[15]:讓頁面實現(xiàn) Pending -> Skeleton -> Complete 的更新路徑, 用戶在切換頁面時可以停留在當前頁面,讓頁面保持響應。相比展示一個無用的空白頁面或者加載狀態(tài),這種用戶體驗更加友好。

          其中 Suspense 可以用來解決請求阻塞的問題,UI 卡頓的問題其實開啟 concurrent mode 就已經解決的,但如何利用 concurrent mode 來實現(xiàn)更友好的交互還是需要對代碼做一番改動的。

          資料參考:Concurrent 模式介紹 (實驗性)[16] | 理解 React Fiber & Concurrent Mode[17] | 11.concurrent mode(并發(fā)模式是什么樣的)[18] | 人人都能讀懂的react源碼解析[19]

          未來可期

          Concurrent Mode只是并發(fā),既然任務可拆分(只要最終得到完整effect list就行),那就允許并行執(zhí)行,(多個Fiber reconciler + 多個worker),首屏也更容易分塊加載/渲染(vDOM森林。

          并行渲染的話,據(jù)說Firefox測試結果顯示,130ms的頁面,只需要30ms就能搞定,所以在這方面是值得期待的,而React已經做好準備了,這也就是在React Fiber上下文經常聽到的待unlock的更多特性之一。

          isInputPending —— Fiber架構思想對前端生態(tài)的影響

          Facebook 在 Chromium 中提出并實現(xiàn)了 isInputPending() API,它可以提高網頁的響應能力,但是不會對性能造成太大影響。Facebook 提出的 isInputPending API 是第一個將中斷的概念用于瀏覽器用戶交互的的功能,并且允許 JavaScript 能夠檢查事件隊列而不會將控制權交于瀏覽器。

          目前 isInputPending API 僅在 Chromium 的 87 版本開始提供,其他瀏覽器并未實現(xiàn)。

          isInputPending

          資料參考:Facebook 將對 React 的優(yōu)化實現(xiàn)到了瀏覽器![20]

          Svelte 對固有模式的沖擊

          當下前端領域,三大框架React、Vue、Angular版本逐漸穩(wěn)定,如果說前端行業(yè)會出現(xiàn)哪些框架有可能會挑戰(zhàn)React或者Vue呢?很多人認為Svelte 應該是其中的選項之一。

          Svelte叫法是[Svelte], 本意是苗條纖瘦的,是一個新興熱門的前端框架。在開發(fā)者滿意度、興趣度、市場占有率上均名列前茅,同時,它有更小的打包體積,更少的開發(fā)代碼書寫,在性能測評中,與React、Vue相比,也不遑多讓。

          Svelte 的核心思想在于『通過靜態(tài)編譯減少框架運行時的代碼量』。

          Svelte 優(yōu)勢有哪些

          • No Runtime —— 無運行時代碼
          • Less-Code —— 寫更少的代碼
          • Hight-Performance —— 高性能

          Svelte 劣勢

          • 社區(qū)
          • 社區(qū)
          • 社區(qū)

          原理概覽

          Svelte 在編譯時,就已經分析好了數(shù)據(jù) 和 DOM 節(jié)點之間的對應關系,在數(shù)據(jù)發(fā)生變化時,可以非常高效的來更新DOM節(jié)點。

          • Rich Harris 在進行Svelte的設計的時候沒有采用 Virtual DOM,主要是因為他覺得Virtual DOM Diff 的過程是非常低效的。具體可參考Virtual Dom 真的高效嗎[21]一文;Svelte 采用了Templates語法,在編譯的過程中就進行優(yōu)化操作;
          • Svelte 記錄臟數(shù)據(jù)的方式:位掩碼(bitMask);
          • 數(shù)據(jù)和DOM節(jié)點之間的對應關系:React 和 Vue 是通過 Virtual Dom 進行 diff 來算出來更新哪些 DOM 節(jié)點效率最高。Svelte 是在編譯時候,就記錄了數(shù)據(jù) 和 DOM 節(jié)點之間的對應關系,并且保存在 p 函數(shù)中。
          數(shù)據(jù)和DOM節(jié)點之間的對應關系

          資料參考:[新興前端框架 Svelte 從入門到原理](

          參考資料

          [1]

          React技術揭秘: https://link.segmentfault.com/?url=https%3A%2F%2Freact.iamkasong.com%2F

          [2]

          前端工程師的自我修養(yǎng):React Fiber 是如何實現(xiàn)更新過程可控的: https://link.segmentfault.com/?url=https%3A%2F%2Fwww.zoo.team%2Farticle%2Fabout-react-fiber

          [3]

          以 React 為例,說說框架和性能(下): https://link.segmentfault.com/?url=https%3A%2F%2Fgitbook.cn%2Fm%2Fmazi%2Fcolumns%2F5c91c813968b1d64b1e08fde%2Ftopics%2F5cbbf49bbbbba80861a35c64

          [4]

          React哲學: https://link.segmentfault.com/?url=https%3A%2F%2Fzh-hans.reactjs.org%2Fdocs%2Fthinking-in-react.html

          [5]

          JSX 和手寫的 render function: https://link.segmentfault.com/?url=https%3A%2F%2Fcn.vuejs.org%2Fv2%2Fguide%2Frender-function.html

          [6]

          以 React 為例,說說框架和性能(下): https://link.segmentfault.com/?url=https%3A%2F%2Fgitbook.cn%2Fm%2Fmazi%2Fcolumns%2F5c91c813968b1d64b1e08fde%2Ftopics%2F5cbbf49bbbbba80861a35c64

          [7]

          React17新特性:啟發(fā)式更新算法: https://link.segmentfault.com/?url=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F182411298

          [8]

          requestIdleCallback 的 FPS 只有 20: https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Ffacebook%2Freact%2Fissues%2F13206

          [9]

          requestIdleCallback-后臺任務調度: https://link.segmentfault.com/?url=http%3A%2F%2Fwww.zhangyunling.com%2F702.html

          [10]

          兩張對比圖: https://link.segmentfault.com/?url=https%3A%2F%2Fclaudiopro.github.io%2Freact-fiber-vs-stack-demo%2F

          [11]

          Stack Example: https://link.segmentfault.com/?url=https%3A%2F%2Fclaudiopro.github.io%2Freact-fiber-vs-stack-demo%2Fstack.html

          [12]

          Fiber Example: https://link.segmentfault.com/?url=https%3A%2F%2Fclaudiopro.github.io%2Freact-fiber-vs-stack-demo%2Ffiber.html

          [13]

          完全理解React Fiber: https://link.segmentfault.com/?url=http%3A%2F%2Fwww.ayqy.net%2Fblog%2Fdive-into-react-fiber%2F

          [14]

          Suspense: https://link.segmentfault.com/?url=https%3A%2F%2Fjuejin.cn%2Fpost%2F6844903981999718407

          [15]

          useTrasition: https://link.segmentfault.com/?url=https%3A%2F%2Fjuejin.cn%2Fpost%2F6844903986420514823

          [16]

          Concurrent 模式介紹 (實驗性): https://link.segmentfault.com/?url=https%3A%2F%2Fzh-hans.reactjs.org%2Fdocs%2Fconcurrent-mode-intro.html

          [17]

          理解 React Fiber & Concurrent Mode: https://link.segmentfault.com/?url=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F109971435

          [18]

          11.concurrent mode(并發(fā)模式是什么樣的): https://link.segmentfault.com/?url=https%3A%2F%2Fxiaochen1024.com%2Farticle_item%2F600acd69245877002ed5df05

          [19]

          人人都能讀懂的react源碼解析: https://link.segmentfault.com/?url=https%3A%2F%2Fxiaochen1024.com%2F

          [20]

          Facebook 將對 React 的優(yōu)化實現(xiàn)到了瀏覽器!: https://link.segmentfault.com/?url=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2FLbcu1aa2LQZlddAwIIExqA

          [21]

          Virtual Dom 真的高效嗎: https://link.segmentfault.com/?url=https%3A%2F%2Fwww.sveltejs.cn%2Fblog%2Fvirtual-dom-is-pure-overhead




          本文為公眾號【前端Sharing】

          關注我,閱讀更多精彩內容

          ▽▽▽

          創(chuàng)作不易,加個點贊、在看 支持一下哦!

          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  亚洲国产色情 | 午夜久久久久久 | 国产一卡二无码 | 99香蕉精品99久久久久久 | 欧美高清中文字幕精品日韩不卡国产在线 |