React fiber原理解析及自定義實現(一)

概述

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

Fiber就是React提出的用於解決頁面卡頓的方案,包含以下三個方面:算法

  1. 利用瀏覽器的空閒時間執行任務,不會長時間佔用主線程。
  2. 由於利用了空閒時間執行任務,因此任務須要能夠被隨時中斷,而迭代是沒法中斷的,循環是隨時能夠中斷的,所以用循環替代迭代。
  3. 將對比更新操做拆分紅一個個小的任務。

核心API

requestIdleCallback 是瀏覽器提供的API,其利用瀏覽器的空閒時間執行任務,若是有更高優先級的任務須要執行時,當前執行的任務可會被終止,優先執行更高優先級的任務。數組

requestIdleCallback接受一個函數做爲參數,該函數是要執行的任務:瀏覽器

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

API示例

在下面的html實例中,頁面上包含兩個按鈕,點擊第一個按鈕執行一段耗時操做,點擊另外一個按鈕alert顯示一段內容:dom

<!DOCTYPE html>
<html>

<head>
    <meta charset='utf-8'>
    <meta http-equiv='X-UA-Compatible' content='IE=edge'>
    <title>Page Title</title>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
</head>

<body>
    <button id="work">long time work</button>
    <button id="interaction">another work</button>
</body>
<script>
    var workBtn = document.getElementById("work")
    var interactionBtn = document.getElementById("interaction")
    var iterationCount = 100000000
    var value = 0

    // 模擬一段耗時操做
    workBtn.addEventListener("click", function () {
        while (iterationCount > 0) {
            value =
                Math.random() < 0.5 ? value + Math.random() : value + Math.random()
            iterationCount = iterationCount - 1
        }
    })

    interactionBtn.addEventListener("click", function () {
        alert('done another work')
    })
</script>

</html>

當點擊第一個按鈕以後迅速點擊第二個按鈕,會發現頁面會卡頓一段時間以後才執行alert。函數

當用requestIdleCallback API改造以後:動畫

<!DOCTYPE html>
<html>

<head>
    <meta charset='utf-8'>
    <meta http-equiv='X-UA-Compatible' content='IE=edge'>
    <title>Page Title</title>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
</head>

<body>
    <button id="work">long time work</button>
    <button id="interaction">another work</button>
</body>
<script>
    var workBtn = document.getElementById("work")
    var interactionBtn = document.getElementById("interaction")
    var iterationCount = 100000000
    var value = 0

    // 模擬一段耗時操做
    var expensiveCalculation = function (IdleDeadline) {
        // 空閒時間超過1秒才執行
        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 () {
        alert('done another work')
    })
</script>

</html>

再次快速點擊第一個按鈕和第二個按鈕,會發現,頁面迅速alert一段信息,說明第一個任務並無阻塞第二個任務。ui

思路

Fiber將Dom對比算法分解成兩步:線程

  1. 構建Fiber對象(可理解成一個個小的任務對象),這個過程能夠隨時被中斷。
  2. 提交:將Fiber對象渲染成真實Dom,這個過程是不能夠被中斷的。

Fiber對象

爲了可以模擬實現整個Fiber的核心代碼,須要首先了解Fiber對象的結構,Fiber對象是一個普通的js對象,其包含以下屬性:code

屬性名 說明
type 節點類型,和虛擬Dom對象的type相同,用於區分元素、文本、組件
props 節點屬性,同虛擬Dom對象
stateNode 節點Dom對象或者組件實例
tag 標記,用於標記節點
effects 存儲包含自身和全部後代的Fiber數組
effectTag 標記當前節點須要進行的操做,包含插入、更新、移除等
parent 父Fiber對象,在React源碼中叫Return
child 當前Fiber對象的子級Fiber對象
sibling 當前Fiber對象的下一級兄弟節點
alternate Fiber對象備份,用於對比

最終虛擬Dom樹會被轉換成Fiber對象的樹形結構數據,最頂層的節點effects屬性中包含了該樹結構全部的Fiber對象,其是一個數組,也就是前文說的能被中斷的一個個小任務的任務操做對象。

相關文章
相關標籤/搜索