😆 Hi,大家好,我是张玥卿云,一个面向未来编程的程序员!准备将 React 源码解析写成一系列文章分享给大家,希望大家喜欢~
一、前言
一个最简单的 React 程序往往包含三个步骤:
- 创建 React 元素树:
const element = React.createElement("div", {}, '');
- 创建 FiberRoot 节点并挂载到 DOM 容器节点上:
const root = ReactDOM.createRoot(container);
- FiberRoot 渲染第一步创建的 React 元素树:
root.render(element);
今天我们详细讲解的是 FiberRoot 的创建和挂载。
二、FiberRoot
FiberRoot 是 React 应用的总控根节点,React 通过创建 FiberRoot 并将其挂载到 DOM 容器元素上来初始化它的工作环境。创建 FbierRoot 的 Api 为 React.createRoot,我们直接来看一下这个 Api。
2.1 React.createRoot
React.createRoot 函数负责创建和挂载 FiberRoot ,它的执行流程如下:
- 处理 options 中一些可配置的属性
- 创建 FiberRoot
- 将 FiberRoot 挂载到了 DOM 容器节点上
- 初始化 Dispatcher
- 获取并监听该监听的 DOM 容器节点的所有事件
- 返回 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 函数主要做了两件事:
- 设置 hydrate 和 initialChildren 两个属性
- 调用 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 树), 并做了一些初始化的操作,具体如下:
- 创建 FiberRoot
- 为 FiberRoot 设置 hydrationCallbacks 和 transitionCallbacks
- 创建 HostRootFiber
- 将 FiberRoot.current 设置为 HostRootFiber
- 将 HostRootFiber 的 stateNode 设置为 FiberRoot
- 初始化 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 的创建函数,主要做了以下几件事:
- 设置 React Fiber 架构的工作模式 (Concurrent 模式、严格模式、ConcurrentUpdatesByDefaultMode 模式)。
- 设置性能分析的模式。
- 创建 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@ ~
😈 如感兴趣,可联系本人:张玥卿云
- 电话: 16602927079
- 邮箱: zhangyueqingyun@foxmail.com
- 微信: abcde-ovo-yz