mini-react新版本fiber架構

以前寫了一篇stack版的mini-react實現,這裏再寫一篇fiber版的實現。

這裏若是不知道二者的區別的話,推薦先看看我這一篇文章:
stack和fiber架構的區別css

從我上面鏈接這篇文章咱們能夠知道:React 16 以前的版本比對更新 VirtualDOM 的過程是採用循環加遞歸實現的,這種比對方式有一個問題,就是一旦任務開始進行就沒法中斷,若是應用中組件數量龐大,主線程被長期佔用,直到整棵 VirtualDOM 樹比對更新完成以後主線程才能被釋放,主線程才能執行其餘任務。這就會致使一些用戶交互,動畫等任務沒法當即獲得執行,頁面就會產生卡頓, 很是的影響用戶體驗。 html

其主要問題是:遞歸沒法中斷,執行重任務耗時長。 JavaScript 又是單線程,沒法同時執行其餘任務,致使任務延遲頁面卡頓,用戶體驗差。node

咱們得解決方案是:react

  1. 利用瀏覽器空閒時間執行任務,拒絕長時間佔用主線程
  2. 放棄遞歸只採用循環,由於循環能夠被中斷
  3. 任務拆分,將任務拆分紅一個個的小任務

基於以上幾點,在這裏咱們先了解下requestIdleCallback這個api算法

核心 API 功能介紹:利用瀏覽器的空餘時間執行任務,若是有更高優先級的任務要執行時,當前執行的任務能夠被終止,優先執行高級別任務。segmentfault

requestIdleCallback(function(deadline) {
  // deadline.timeRemaining() 獲取瀏覽器的空餘時間
})

這裏咱們瞭解下什麼是瀏覽器空餘時間:頁面是一幀一幀繪製出來的,當每秒繪製的幀數達到 60 時,頁面是流暢的,小於這個值時, 用戶會感受到卡頓,1s 60幀,每一幀分到的時間是 1000/60 ≈ 16 ms,若是每一幀執行的時間小於16ms,就說明瀏覽器有空餘時間。api

若是任務在剩餘的時間內沒有完成則會中止任務執行,繼續優先執行主任務,也就是說 requestIdleCallback 老是利用瀏覽器的空餘時間執行任務。數組

咱們先用這個api作個例子,來看:
html瀏覽器

<div class="playground" id="play">playground</div>
<button id="work">start work</button>
<button id="interaction">handle some user interaction</button>

css架構

<style>
  .playground {
    background: palevioletred;
    padding: 20px;
    margin-bottom: 10px;
  }
</style>

js

var play = document.getElementById("play")
var workBtn = document.getElementById("work")
var interactionBtn = document.getElementById("interaction")
var iterationCount = 100000000
var value = 0

var expensiveCalculation = function (IdleDeadline) {
  while (iterationCount > 0 && IdleDeadline.timeRemaining() > 1) {
    value =
      Math.random() < 0.5 ? value + Math.random() : value + Math.random()
    iterationCount = iterationCount - 1
  }
  requestIdleCallback(expensiveCalculation)
}

workBtn.addEventListener("click", function () {
  requestIdleCallback(expensiveCalculation)
})

interactionBtn.addEventListener("click", function () {
  play.style.background = "palegreen"
})

從這個示例中咱們知道了,這個api該如何使用,該如何中斷任務。

而後咱們來實現咱們得fiber架構得react-mini版本。

在 Fiber 方案中,爲了實現任務的終止再繼續,DOM比對算法被分紅了兩部分:

  1. 構建 Fiber (可中斷)
  2. 提交 Commit (不可中斷,更新dom)

目前咱們設計得fiber對象有如下屬性:

{
  type         節點類型 (元素, 文本, 組件)(具體的類型)
  props        節點屬性
  stateNode    節點 DOM 對象 | 組件實例對象
  tag          節點標記 (對具體類型的分類 hostRoot || hostComponent || classComponent || functionComponent)
  effects      數組, 存儲須要更改的 fiber 對象
  effectTag    當前 Fiber 要被執行的操做 (新增, 刪除, 修改)
  parent       當前 Fiber 的父級 Fiber
  child        當前 Fiber 的子級 Fiber
  sibling      當前 Fiber 的下一個兄弟 Fiber
  alternate    Fiber 備份 fiber 比對時使用
}

image.png

這時咱們的項目結構

react/index.js 是入口文件,在這裏咱們對它作react主要使用api的導出

import createElement from "./CreateElement"
export { render } from "./reconciliation"
export { Component } from "./Component"

export default {
  createElement
}

而後咱們先來寫createElement 用來把jsx生成 vnode的函數:
react/CreateElement

export default function createElement(type, props, ...children) {
  const childElements = [].concat(...children).reduce((result, child) => {
    if (child !== false && child !== true && child !== null) {
      if (child instanceof Object) {
        result.push(child)
      } else {
        result.push(createElement("text", { textContent: child }))
      }
    }
    return result
  }, [])
  return {
    type,
    props: Object.assign({ children: childElements }, props)
  }
}

這個的實現是和stack架構同樣的

接下來就是咱們的render函數,reconciliation/index.js 這裏也就是咱們核心的協調算法了.

在這裏咱們主要工做是分兩個階段:
1.根據vnode來生成fiber對象,生成fiber的過程是經過循環來生成的,由於咱們生成fiber以及它的子集fiber的過程是能夠被打斷的,因此咱們經過循環的方式來生成,也就是說咱們如今的fiber的結構是一個鏈表的結構.
image.png

這是一個樹的結構,在咱們生成的fiber對象中,會有parent指向父節點,child指向咱們當前子集的第一個的節點,也就是最左側的節點,子集中其餘節點是咱們這個child的兄弟節點sibling。

每次構建一個子集就是一個子任務,fiber任務是能夠被打斷的。

2.咱們每次構建fiber會把當前fiber以及自身收集到的子集fiber的effects數組放入父級的effects數組中,而後第二個階段是commit生成dom階段,咱們只須要循環最外層的fiber中effects數組就能夠,這個數組中放着全部的fiber對象,咱們只須要依次把它們的stateNode 也就是dom對象根據effectTag操做符,來對比更新,刪除新增到它的parent父級的stateNode中.

這樣咱們一個完整流程就完成了.

相關文章
相關標籤/搜索