剖析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进入下一(渲染)阶段。

相关文章
相关标签/搜索