React是如何启动的
date
Jan 9, 2022
slug
react-qidong
status
Published
tags
React
源码
summary
从源码解析React启动过程
type
Post
本文基于react17.0.0
在贴源码前,先简单介绍下react的三种启动模式:
- legacy mode:通过
ReactDOM.render(<App/>, root)
进入,不支持一些新特性
- blocking mode: 通过
ReactDOM.createBlokingRoot().render(<App/>,)
进入,支持concurrent mode
中的部分特性
- concurrent mode: 通过
ReactDOM.createRoot().render(<App/>)
进入,支持所有新特性
tips:
- 其中
blocking mode
和concurrent mode
均在实验阶段,在react18中,后者将正式成为一个可选的mode
- 虽然
createBlockingMode
和createRoot
存在于17.x的原码中,但ReactDOM并没有将它们暴露出来,也就是说想体验这两个模式得将react安装为体验版
legacy mode
当我们调用
ReactDOM.render
时,内部会调用legacyRenderSubtreeIntoContainer
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: Container,
forceHydrate: boolean,
callback: ?Function,
) {
let root: RootType = (container._reactRootContainer: any);
let fiberRoot;
if (!root) {
// 初次挂载时
// container指ReactDOM.render的第二个参数(即应用挂载的DOM节点)
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
// instance最终指向 children(入参: 如<App/>)生成的DOM节点
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// 调用更新,第一次更新不需要批量
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
// root已经初始化过的情况
// 获取FiberRoot对象
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// 调用更新
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
在首次挂载时,会调用
legacyCreateRootFromDOMContainer
,该方法内部会调用createLegacyRoot
,它内部会执行new ReactDOMBlockingRoot
并返回function legacyCreateRootFromDOMContainer(
container: Container,
forceHydrate: boolean,
): RootType {
// ...处理hydrate
return createLegacyRoot(
container,
shouldHydrate
? {
hydrate: true,
}
: undefined,
);
}
export function createLegacyRoot(
container: Container,
options?: RootOptions,
): RootType {
return new ReactDOMBlockingRoot(container, LegacyRoot, options); // 注意这里的LegacyRoot是固定的, 并不是外界传入的
}
在
ReactDOMBlockingRoot
中,会通过调用createRootImpl
,创建一个fiberRoot
对象,并挂载到this._internalRoot
上;此外,会在ReactDOMBlockingRoot
的原型上挂载render
和unmount
方法function ReactDOMBlockingRoot(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
this._internalRoot = createRootImpl(container, tag, options);
}
ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function(
children: ReactNodeList,
): void {
const root = this._internalRoot;
// 调用更新
updateContainer(children, root, null, null);
};
ReactDOMRoot.prototype.unmount = ReactDOMBlockingRoot.prototype.unmount = function(): void {
const root = this._internalRoot;
const container = root.containerInfo;
// 调用更新
updateContainer(null, root, null, () => {
unmarkContainerAsRoot(container);
});
};
继续看
createRootImpl
,内部主要做了三件事:- 通过
createContainer
创建fiberRoot
- 标记DOM,将DOM和fiber对象关联起来
- 在该节点上进行事件监听
function createRootImpl(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
// ...hydrate部分
// 创建fiberRoot
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
// 标记DOM,把DOM和fiber对象关联起来
markContainerAsRoot(root.current, container);
// COMMENT_NODE = 8,代表注释,nodeType = 1, 代表元素
// nodeType详细可看https://www.w3school.com.cn/jsref/prop_node_nodetype.asp
const rootContainerElement =
container.nodeType === COMMENT_NODE ? container.parentNode : container;
// 在该节点上进行事件监听
listenToAllSupportedEvents(rootContainerElement);
// 省略不相干代码
return root;
}
createContainer
的内部调用了createFiberRoot
来创建fiberRoot
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {
// 创建fiberRoot对象
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks); // tag在三种mode下不同:ConcurrentRoot,BlockingRoot,LegacyRoot
}
createFiberRoot
内部,创建了react中的第一个fiber对象:HostRootFiber
export function createFiberRoot(
// containerInfo就是根节点,如<div id='app'></div>
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
// 创建fiberRootNode
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// 创建了react中的第一个fiber对象
const uninitializedFiber = createHostRootFiber(tag);
// root.current指向rootFiber,root.current指向哪棵Fiber树,页面上就显示该Fiber树对应的dom
root.current = uninitializedFiber;
// rootFiber.stateNode指向FiberRoot,可通过stateNode.containerInfo取到对应的dom根节点div#root
uninitializedFiber.stateNode = root;
// 初始化updateQueue,对于RootFiber,queue.shared.pending上面存储着React.element
initializeUpdateQueue(uninitializedFiber);
return root;
}
在创建
HostRootFiber
时,mode
的值会关联传入的tag
(ConcurrentRoot, BlockingRoot, LegacyRoot),而在后续的每个fiber
节点创建时,fiber.mode
属性都会来自于父节点export function createHostRootFiber(tag: RootTag): Fiber {
let mode;
if (tag === ConcurrentRoot) {
mode = ConcurrentMode | BlockingMode | StrictMode;
} else if (tag === BlockingRoot) {
mode = BlockingMode | StrictMode;
} else {
mode = NoMode;
}
// ...
const newFiber = createFiber(HostRoot, null, null, mode);
return newFiber
}
而
createFiber
内部,则会创建一个FiberNode
const createFiber = function(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
): Fiber {
const fiberNode = new FiberNode(tag, pendingProps, key, mode);
return fiberNode
};
至此,react的初始化工作就做完了,接下来会调用
updateContainer
进入首次更新,legacy mode下
,会将updateContainer
传入unbatchedUpdates
中,它的作用是将执行上下文修改为LegacyUnbatchedContext
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
updateContainer
内部(开始更新,进入了reconciler
的职责):export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
const current = container.current;
// 获取事件开始时间,一般是performance.now()
const eventTime = requestEventTime();
// 获取本次更新优先级
const lane = requestUpdateLane(current);
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
// 创建更新任务
const update = createUpdate(eventTime, lane);
// 对于container,其update的payload就是React.element
update.payload = { element };
// 对于ReactDOM.createRoot(Concurrent模式)的render来说,callback为null
// 对于ReactDOM.render来说,callback为ReactDOM.render的第三个参数
callback = callback === undefined ? null : callback;
if (callback !== null) {
update.callback = callback;
}
// 往updateQueue加入update
enqueueUpdate(current, update);
// 调度更新
scheduleUpdateOnFiber(current, lane, eventTime);
return lane;
}
总之,在
ReactDOM.render
后,会进行如下操作:- 对react的初始化
- 创建
ReactBlockingRoot
对象,挂载到this._internalRoot
上,该对象有render
和unmount
方法
- 创建
fiberRoot
对象,作为reconciler
运行时的全局上下文,保存fiber
构建时需要的状态
- 创建
HostRootFiber
对象,fiber tree
的根节点,类型为HostRoot
- 调度更新,进入
reconciler
的工作职责,本文不深入