React 源码解析:第二章 FiberRoot 的创建与挂载

785 阅读2分钟

😆 Hi,大家好,我是张玥卿云,一个面向未来编程的程序员!准备将 React 源码解析写成一系列文章分享给大家,希望大家喜欢~

一、前言

一个最简单的 React 程序往往包含三个步骤:

  1. 创建 React 元素树:
const element = React.createElement("div", {}, '');
  1. 创建 FiberRoot 节点并挂载到 DOM 容器节点上:
const root = ReactDOM.createRoot(container);
  1. FiberRoot 渲染第一步创建的 React 元素树:
root.render(element);

今天我们详细讲解的是 FiberRoot 的创建和挂载。

二、FiberRoot

FiberRoot 是 React 应用的总控根节点,React 通过创建 FiberRoot 并将其挂载到 DOM 容器元素上来初始化它的工作环境。创建 FbierRoot 的 Api 为 React.createRoot,我们直接来看一下这个 Api。

2.1 React.createRoot

React.createRoot 函数负责创建和挂载 FiberRoot ,它的执行流程如下:

  1. 处理 options 中一些可配置的属性
  2. 创建 FiberRoot
  3. 将 FiberRoot 挂载到了 DOM 容器节点上
  4. 初始化 Dispatcher
  5. 获取并监听该监听的 DOM 容器节点的所有事件
  6. 返回 ReactDOMRoot
export function createRoot(
    container,
    options
) {
    if(!isValidContainer(container)) {
        throw new Error('createRoot(...): Target container is not a DOM element');
    }

    let isStrictMode = false;    // 严格模式
    let concurrentUpdatesByDefaultOverride = false;    // 设置更新模式
    let identifierPrefix = '';    //  前缀
    let onRecoverableError = defaultOnRecoverableError;  // 可恢复的错误处理方法
    let transitionCallbacks = null;    // 过度回调

    if(options !== null && options !== undefined) {
        /** 设置严格模式 */
        if(options.unstable_strictMode === true) {
            isStrictMode = true;
        }

        /** 设置 ConcurrentUpdatesByDefaultMode 为 true  */ 
        if(
            allowConcurrentByDefault &&
            options.unstable_concurrentUpdatesByDefault === true
        ) {
            concurrentUpdatesByDefaultOverride = true;
        }

        /** 设置前缀 */
        if(options.identifierPrefix !== undefined) {
            identifierPrefix = options..identifierPrefix;
        }

        /** 设置可恢复的错误处理回调 */
        if(options.onRecoverableError !== undefined) {
            onRecoverableError = options.onRecoverableError;
        }

        /** 设置过渡回调 */
        if(options.unstable_transitionCallbacks !== undefined) {
            transitionCallbacks = options.unstable_transitionCallbacks;
        }
    }

    /** 创建 FiberRoot */
    const root = createContainer(
        container,
        ConcurrentRoot,
        null,
        isStrictMode,
        concurrentUpdatesByDefaultOverride,
        identifierPrefix,
        onRecoverableError,
        transitionCallbacks
    );

    /** 将 FiberRoot 挂载到 DOM 节点上 */
    markContainerAsRoot(root.current, container);

    /** 初始化 Dispatcher */
    if(enableFloat) {
        Dispatcher.current = ReactDOMClientDispatcher;
    }

    /** 获取根元素节点 */
    const rootContainerElement = container.nodeType = COMMENT_NODE ? container.parentNode : container;

    /** 监听根元素节点的所有事件 */
    listenToAllSupportedEvents(rootContainerElement);

    /** 返回 ReactDOMRoot */
    return new ReactDOMRoot(root);
}

2.1.1 FiberRoot 的创建

上面讲了创建挂载 FiberRoot 的总控函数 React.createRoot ,现在我们详细看一下 FiberRoot 的创建。

2.1.1.1 FiberRootNode

我们看一下 FiberRoot 的类构造函数 FiberRootNode ,它定义了 FiberRoot 所拥有的属性,这些属性详细的意义将在 Fiber 架构的执行机制彼篇讲解,大家先简单看一下它的源码:

function FiberRootNode(
    containerInfo,
    tag,
    hydrate,
    identifierPrefix,
    onRecoverableError
) {
    this.tag = tag;
    this.containerInfo = containerInfo;    // DOM 容器节点
    this.pendingChildren = null;    
    this.current = null;    // Fiber 树
    this.pingCache = null;
    this.finishedWork = null;    
    this.timeoutHandle = noTimeout;
    this.context = null;
    this.pendingContext = null;
    this.callbackNode = null;
    this.callbackPriority = NoLane;
    this.eventTimes = createLaneMap(NoLanes);

    this.pendingLanes = NoLanes;
    this.suspencedLanes = NoLanes;
    this.pingedLanes = NoLanes;
    this.expiredLanes = NoLanes;
    this.mutableReadLanes = NoLanes;

    this.entangledLanes = NoLanes;
    this.hiddenUpdates = createLaneMap(null);

    this.identifierPrefix = identifierPrefix;
    this.onRecoverableError = onRecoverableError;

    if(enableCache) {
        this.pooledCache = null;
        this.pooledCacheLanes = NoLanes;
    }

    if(supportsHydration) {
        this.mutableSourceEagerHydrationData = null;
    }

    if(enableSuspenseCallback) {
        this.hydrationCallbacks = null;
    }

    this.incompleteTransitions = new Map();
    if(enableTransitionTracking) {
        this.transitionCallbacks = null;
        const transitionLanesMap = (this.transitionLanes = []);
        for(let i = 0; i < TotalLanes; i++) {
            pendingUpdatersLaneMap.push(new Set())
        }
    }
}

接下来我们看一下负责创建 FiberRoot 的函数 createContainer 和 createFiberRoot。

2.1.1.2 createContainer

createContainer 函数主要做了两件事:

  1. 设置 hydrate 和 initialChildren 两个属性
  2. 调用 createFiberRoot 函数创建 FiberRoot

hydrate 是服务端渲染注水操作的标志,被设置为 false,initialChildren 是初始化的子节点,被设置为 null 。

export function createContainer(
    containerInfo,
    tag,
    hydrationCallbacks,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks
) {
    const hydrate = false;    // 服务段渲染相关
    const initialChildren = null;    // 初始子节点

    return createFiberRoot(
        containerInfo,
        tag,
        hydrate,
        initialChildren,
        hydrationCallbacks,
        isStrictMode,
        concurrentUpdatesByDefaultOverride,
        identifierPrefix,
        onRecoverableError,
        transitionCallbacks
    )
}
2.1.1.3 createFiberRoot

createFiberRoot 创建了 FiberRoot 和 HostRootFiber (尚未初始化的 Fiber 树), 并做了一些初始化的操作,具体如下:

  1. 创建 FiberRoot
  2. 为 FiberRoot 设置 hydrationCallbacks 和 transitionCallbacks
  3. 创建 HostRootFiber
  4. 将 FiberRoot.current 设置为 HostRootFiber
  5. 将 HostRootFiber 的 stateNode 设置为 FiberRoot
  6. 初始化 HostRootFiber 的更修队列

createFiberRoot 将 FiberRoot 的 current 属性设置为 HostRootFiber,同时也将 HostRootFiber 的 stateNode 属性设置为 FiberRoot ,它们拥有彼此的引用。

export function createFiberRoot(
    containerInfo,
    tag,
    hydrate,
    initialChildren,
    hydrationCallbacks,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks
) {
    /** 创建 FiberRoot */
    const root = new FiberRootNode(
        containerInfo,
        tag,
        hydrate,
        identifierPrefix,
        onRecoverableError
    );

    /** 设置服务端渲染回调 */
    if(enableSuspenseCallback)  {
        root.hydrationCallbacks = hydrationCallbacks;
    }

    /** 设置过渡回调 */
    if(enableTransitionTracing) {
        root.transitionCallbacks = transitionCallbacks;
    }

    /** 创建 HostRootFiber */
    const uninitializedFiber = createHostRootFiber(
        tag,
        isStrictMode,
        concurrentUpdatesByDefaultOverride
    );

    /** 将 HostRootFiber 挂载到 FiberRoot 的 current 属性上 */
    root.current = uninitializedFiber;

    /** 将 HostRootFiber 的 stateNode 设置为 FiberRoot */
    uninitializedFiber.stateNode = root;

    /** 设置 HostRootFiber 的 memoizedState */
    if(enableCache) {
        const initialCache = createCache();
        retainCache(initialCache);
	
        root.pooledCache = initialCache;
        retainCache(initialCache);
        const initialState = {
            element: initialChildren,
            isDehydrated: hydrate,
            cache: initialCache
        };

        uninitializedFiber.memoizedState = initialState;
    } else {
        const initialState:RootState = {
            element: initialChildren,
            isDehydrated: hydrate,
            cache: (null: any)
        };
        uninitializedFiber.memoizedState = initialsTate;
    }

    /** 初始化 HostRootFiber 的更修队列 */
    initializeUpdateQueue(unitializedFiber);

    return root;
}

至此,FiberRoot 就创建完成啦,下面我们将讲解 HostRootFiber 的创建过程。

2.1.2 创建 HostRootFiber

上面提到过,HostRootFiber 对应着 Fiber 架构中未被初始化过的 Fiber 树,它被挂载到 FiberRoot 的 current 属性上,它的 stateNode 属性也被设置为 FiberRoot 。

createHostRootFiber 是 HostRootFiber 的创建函数,主要做了以下几件事:

  1. 设置 React Fiber 架构的工作模式 (Concurrent 模式、严格模式、ConcurrentUpdatesByDefaultMode 模式)。
  2. 设置性能分析的模式。
  3. 创建 Fiber。
export function createHostRootFiber(
    tag,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
)  {
    let mode;

    /** Concurrent 模式  */
    if ( tag === ConcurrentRoot ) {
        mode = ConcurrentMode;

        /** 严格模式 */
        if(isStrictMode === true || createRootStrictEffectsByDefault) {
            mode |= StrictLegacyMode | StrictEffectsMode;
        }

        if( 
            !enableSyncDefaultUpdates || 
            (allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
        ) {
            /** 设置 ConcurrentUpdatesByDefaultMode  模式 */
            mode |= ConcurrentUpdatesByDefaultMode;
        }
	/** 非 Concurrent 模式 */
    } else {
        mode = NoMode;
    }
     
    /** 性能分析模式 */
    if(enableProfilerTimer && isDevToolsPresent) {
        mode |= ProfileMode;
    }

    return createFiber(HostRoot, null, null, mode);
}

经过以上的步骤,FiberRoot 已经被 React 创建完成啦!最后我们来讲解一下 FiberRoot 是怎么被挂载到 DOM 容器节点上的。

2.1.3 FiberRoot 的挂载

markContainerAsRoot 是挂载 FiberRoot 的工作函数,将 Dom 容器节点的 `_reactFiber$${ramdomKey}` 属性设置为 FiberRoot。

源代码如下:

cosnt randomKey = Math.ramdom().toString(36).slice(2);

const internalInstanceKey = '_reactFiber$' + randomKey;

/** 在 DOM 节点上设置属性 '_reactFiber' + ramdomKey 为 FiberRoot */
export function markContainerAsRoot(hostInst, node) {
	node[internalInstanceKey] = hostInst;
} 

2.1.4 ReactDOMRoot

最后我们来看一下 ReactDOM.createRoot 的返回值 ReactDOMRoot。

ReactDOMRoot 提供了 render 和 unmount 两个方法。它将 FiberRoot 设置为其内部属性 _internalRoot,通过 FiberRoot 完成渲染和卸载的工作,源代码如下:

function ReactDOMRoot(internalRoot) {
    /** FiberRoot */
    this._internalRoot = internalRoot;
}

/** 利用 FiberRoot 渲染 ReactElement */
ReactDOMRoot.prototype.render = function (children) {
    /** FiberRoot */
    const root = this._internalRoot;
    if(root === null) {
        throw new Error('Cannot update an unmounted root.');
    }
    
    /** 渲染 */
    updateContainer(children, root, null, null);
}

/** 卸载 FiberRoot */
ReactDOMRoot.prototype.unmount = function(): void {
    /** FiberRoot */
    const root = this._internalRoot;
    if (root !== null) {
        this._internalRoot = null;
        const container = root.containerInfo;

        /** 清空 Fiber 树 */
        flushSync(() => {
            updateContainer(null, root, null, null);
        });
        /** 将 FiberRoot 从 DOM 容器元素上卸载 */
        unmarkContainerAsRoot(container);
    }
};

三、结尾

这些就是 FiberRoot 创建及挂载的全过程啦! 欢迎小伙伴们一起讨论~ @V@ ~

😈 如感兴趣,可联系本人:张玥卿云

🐼 其他