剖析React內部運行機制-應用程序「首次渲染」到瀏覽器-建立fiberRoot

在閱讀本文以前,建議先看一看下面這幾篇文章:react

須要明確的幾個概念

React組件: 開發者寫的組件(class類型或者function類型等)。在執行時會被React解析成React元素。算法

React元素: 描述組件實例或DOM節點及其所需屬性的普通對象。bash

DOM元素: DOM節點對象。架構

在React執行內部,上面三者之間的轉換關係是下面這樣的:app

React組件 --> React元素 --> DOM元素
複製代碼

React組件之間能夠層層嵌套會造成「組件樹」,與之對應的就是:dom

React組件樹 --> React Fiber樹 --> DOM
複製代碼

React元素在React執行時會被放入到React Fiber對象中。函數

用於分析「渲染」流程的demo

app.js源碼分析

import React from 'react';
import {render} from 'react-dom';
import HelloReact from './HelloReact';

render(<HelloReact name="Taylor" />, document.getElementById('root')); 複製代碼

HelloReact.jspost

class HelloReact extends Component{
    render() {
        return (
            <div> <span>Hello {this.props.name}</span> </div>
        );
    }
}

export default HelloReact
複製代碼

程序「首次渲染」過程

咱們將程序的「首次渲染」過程分爲三個階段:構建fiberRoot渲染(render) fiberRoot提交(commit) fiberRootui

構建FiberRoot階段

fiberRoot是FiberRootNode的實例,其構造函數爲

function FiberRootNode(containerInfo, tag, hydrate) {
    this.tag = tag;
    this.current = null;
    this.containerInfo = containerInfo; // DOM容器元素,即document.getElementById('root')
    this.pendingChildren = null;
    this.pingCache = null;
    this.finishedExpirationTime = NoWork;
    this.finishedWork = null; // 執行到後面會被賦值由組件樹解析而成的fiber樹
    this.timeoutHandle = noTimeout;
    this.context = null;
    this.pendingContext = null;
    this.hydrate = hydrate;
    this.firstBatch = null;
    this.callbackNode = null;
    this.callbackExpirationTime = NoWork;
    this.firstPendingTime = NoWork;
    this.lastPendingTime = NoWork;
    this.pingTime = NoWork;

    if (enableSchedulerTracing) {
        this.interactionThreadID = tracing.unstable_getThreadID();
        this.memoizedInteractions = new Set();
        this.pendingInteractionMap = new Map();
    }
}
複製代碼

fiberRoot的產生入口-legacyRenderSubtreeIntoContainer

function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
    ...
    var root = container._reactRootContainer; // 首次加載時返回值undefined
    var fiberRoot = void 0;
    if (!root) {
        // 首次加載時賦值邏輯
        root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
        fiberRoot = root._internalRoot;
        // 首次加載不能批量更新.
        unbatchedUpdates(function () {
            updateContainer(children, fiberRoot, parentComponent, callback);
        });
    }
    ...
}
複製代碼

fiberRoot建立函數-legacyCreateRootFromDOMContainer

function legacyCreateRootFromDOMContainer(container, forceHydrate) {
    ...
    return new ReactSyncRoot(container, LegacyRoot, shouldHydrate);
}
複製代碼

ReactSyncRoot構造函數

function ReactSyncRoot(container, tag, hydrate) {
    var root = createContainer(container, tag, hydrate);
    this._internalRoot = root;
}
複製代碼

createContainer函數

function createContainer(containerInfo, tag, hydrate) {
    return createFiberRoot(containerInfo, tag, hydrate);
}
複製代碼

createFiberRoot函數

function createFiberRoot(containerInfo, tag, hydrate) {
    var root = new FiberRootNode(containerInfo, tag, hydrate);
    
    // 循環結構,這就欺騙了類型系統,由於stateNode是任意類型
    var uninitializedFiber = createHostRootFiber(tag);
    root.current = uninitializedFiber;
    uninitializedFiber.stateNode = root;
    
    return root;
}
複製代碼

createHostRootFiber函數

function createHostRootFiber(tag) {
    var mode = void 0;
    if (tag === ConcurrentRoot) {
        mode = ConcurrentMode | BatchedMode | StrictMode;
    } else if (tag === BatchedRoot) {
        mode = BatchedMode | StrictMode;
    } else {
        mode = NoMode;
    }
    
    ...

    return createFiber(HostRoot, null, null, mode);
}
複製代碼

createFiber函數

var createFiber = function (tag, pendingProps, key, mode) {
    return new FiberNode(tag, pendingProps, key, mode);
};
複製代碼

FiberNode構造函數

function FiberNode(tag, pendingProps, key, mode) {
    // 此處屬性用於生成DOM節點實例
    this.tag = tag; // 用於標識組件的類型,好比class組件、function組件、宿主組件等
    this.key = key;
    this.elementType = null;
    this.type = null;
    this.stateNode = null;
    
    // 此處屬性用於協調算法遍歷Fiber節點樹
    this.return = null;
    this.child = null;
    this.sibling = null;
    this.index = 0;
    
    this.ref = null;
    
    this.pendingProps = pendingProps;
    this.memoizedProps = null;
    this.updateQueue = null;
    this.memoizedState = null;
    this.dependencies = null;
    
    this.mode = mode;
    
    // 此處屬性用於反作用(的調用,好比生命週期函數等)
    this.effectTag = NoEffect;
    this.nextEffect = null;
    
    this.firstEffect = null;
    this.lastEffect = null;
    
    this.expirationTime = NoWork;
    this.childExpirationTime = NoWork;
    
    this.alternate = null;
    
    ...
    ...
}
複製代碼

上面各個函數之間來回調用,關係有些混亂,它們執行結束後獲得的rootfiberRoot對象,其關係像下圖這樣:

fiberRoot對象是FiberRootNode的實例,其current屬性指向了FiberNode實例,FiberNode實例中的stateNode屬性又指向了fiberRoot對象。一種多麼奇妙的循環設計! 這種設計方式在後續文章中進行討論。

接下來開始執行unbatchedUpdates函數:

// 首次渲染不進行批量更新
unbatchedUpdates(function () {
    // 此時的children是React元素而不是fiber對象
    updateContainer(children, fiberRoot, parentComponent, callback);
});
複製代碼

updateContainer函數

function updateContainer(element, container, parentComponent, callback) {
    var current$$1 = container.current;
    var currentTime = requestCurrentTime();
    ...
    var suspenseConfig = requestCurrentSuspenseConfig();
    // 這裏定義了有效時間
    var expirationTime = computeExpirationForFiber(currentTime, current$$1, suspenseConfig);
    return updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, suspenseConfig, callback);
}
複製代碼

updateContainerAtExpirationTime函數

function updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, suspenseConfig, callback) {
    var current$$1 = container.current;
    ...
    var context = getContextForSubtree(parentComponent);
    if (container.context === null) {
    container.context = context;
    } else {
    container.pendingContext = context;
    }
    
    return scheduleRootUpdate(current$$1, element, expirationTime, suspenseConfig, callback);
}
複製代碼

scheduleRootUpdate函數

function scheduleRootUpdate(current$$1, element, expirationTime, suspenseConfig, callback) {
    ...
    // 建立一個「更新」
    var update = createUpdate(expirationTime, suspenseConfig);
    update.payload = { element: element };
    ...
    // 將「更新」加入隊列
    enqueueUpdate(current$$1, update);
    // 開始調度工做
    scheduleWork(current$$1, expirationTime);

    return expirationTime;
}
複製代碼

在開始調度工做時(也就是開始執行scheduleWork函數)的fiberRootcurrent$$1updateQueue屬性已經被加入相應的「更新-update」,其內部以下圖所示:

scheduleWork函數被賦值scheduleUpdateOnFiber函數

var scheduleWork = scheduleUpdateOnFiber;
複製代碼
function scheduleUpdateOnFiber(fiber, expirationTime) {
    ...
    // 在這裏要進入「渲染」階段了
    var callback = renderRoot(root, Sync, true);
    while (callback !== null) {
        callback = callback(true);
    }
    ...
}
複製代碼

小結

在進入「渲染」階段前,React內部建立了fiberRoot,也就是React fiber樹的入口。React會把組件樹的根組件轉換成React元素,而後封裝成update對象並將其加入到updateQueue對象中。

fiberRoot對象的stateNode屬性指向了FiberNode的實例,該實例被賦值到current$$1中。最後current$$1對象攜帶者updateQueue進入下一(渲染)階段。

相關文章
相關標籤/搜索