在react中有3种启动方式:
- ReactDOM.render
- ReactDOM.unstable_createBlockingRoot
- ReactDOM.unstable_createRoot
第一种方式,是17版本,常用的方式,也称legacy模式
第二种方式,还在实验中,支持了部分并发模式下的功能
第三种方式,也称并发模式,目前还在实验中,后续将做为默认模式
一. 启动模式使用
我们先来了解下,各个模式如何coding
render
ReactDOM.render(<App/>, document.getElementById("app"), () => {});
unstable_createBlockingRoot
ReactDOM.unstable_createBlockingRoot(document.getElementById("app")).render(<App/>)
注意, render方法不支持callback
unstable_createRoot
ReactDOM.unstable_createRoot(document.getElementById("app")).render(<App/>)
注意, render方法不支持callback
二. ReactDOM.render
render
function render(element, container, callback) {
// ... 校验container必须是DOM
// ...
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}
其中,element对象是jsx转换后的element对象,container是DOM容器,callback为回调函数
legacyRenderSubtreeIntoContainer
function legacyRenderSubtreeIntoContainer(
parentComponent,
children,
container,
forceHydrate,
callback
) {
// ...
let root: RootType = (container._reactRootContainer: any);
if(!root) {
// 首次渲染,root不存在,将执行下面逻辑
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
}else {
// ... else分支可以先忽略,我们放到后面章节具体分析
// 这里还会涉及 fiberRoot
}
}
我们可以看出,首次渲染,root不存在,将进入创建root阶段。 注意,这里开始涉及Fiber。
本质上,root对象包含 render,unmount函数,以及FiberRoot对象。
这里,有个概念,FiberRoot和RootFiber。 root对象创建的是FiberRoot,他是所有Fiber的根
我们先看legacyCreateRootFromDOMContainer
legacyCreateRootFromDOMContainer
function legacyCreateRootFromDOMContainer(container, forceHydrate) {
// forceHydrate:是否调和复用
const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// 服务端渲染,是需要复用元素的
if(!shouldHydrate) {
// 客户端渲染场景,根root节点下的元素不需要复用,全部删除
// ...
}
return createLegacyRoot(
container,
shouldHydrate
? {
hydrate: true,
}
: undefined,
);
}
createLegacyRoot
function createLegacyRoot(container, options) {
return new ReactDOMBlockingRoot(container, LegacyRoot, options)
}
由于是客户端渲染,options为undefined,其中LegacyRoot为0
ReactDOMBlockingRoot
function ReactDOMBlockingRoot(container, tag, options) {
this._internalRoot = createRootImpl(container, tag, options);
}
createRootImpl
function createRootImpl(container, tag, options) {
// ...
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
// 关联fiber和dom
markContainerAsRoot(root.current, container);
// ...
return root;
}
首次创建,核心createContainer,其他逻辑先跳过...
createContainer的定义,在17版本中,区别createContainer_new和createContainer_old。默认的flags为old
createContainer
function createContainer(containerInfo, tag, hydrate, hydrationCallbacks) {
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
需要说明的是 containerInfo实际上就是container参数,tag = 0,另外2个参数和服务端渲染有关,先忽略
createFiberRoot
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// ...
const uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
initializeUpdateQueue(uninitializedFiber);
return root;
}
这里,做了2件事。 第一,实例化fiberRoot对象。第二,fiber对象的current指向首个fiber对象,而stateNode属性又指向fiberRoot
FiberRoot
终于,这里我们看到了fiberRoot,这个是fiber的后续运作的顶层对象,所有的fiber对象,都在他下面运作。
本质上: fiberRoot是一个构造函数,其结构如下:
function FiberRootNode(containerInfo, tag, hydrate) {
// 渲染模式
this.tag = tag;
// 容器节点
this.containerInfo = containerInfo;
// 持久更新中会使用
this.pendingChildren = null;
// 当前对应的fiber对象,这里将会赋值root fiber
this.current = null;
// ...
// 记录已经渲染完成的任务,因为任务后面分优先级,优先完成优先级高的任务
this.finishedWork = null;
// 这个是异步render,如suspense的fallback,使用setTimeout实现
this.timeoutHandle = noTimeout;
// ...
// 顶层context对象
this.context = null;
// ...
// 是否需要融合
this.hydrate = hydrate;
// eventTimes, expirationTimes .. 这些属性将会在调度中使用,这里先忽略
// lane模型相关属性
// ...这里先忽略
// ...
}
createHostRootFiber
function createHostRootFiber(tag) {
// tag和mode之间的转换
// ...
return createFiber(HostRoot, null, null, mode);
}
注意,这里tag实际上就是渲染模式,在react中,有3种模式,也是我们开篇提到的,其定义如下:
export type RootTag = 0 | 1 | 2;
export const LegacyRoot = 0;
export const BlockingRoot = 1;
export const ConcurrentRoot = 2;
createFiber
function createFiber(tag, pendingProps, key, mode) {
return new FiberNode(tag, pendingProps, key, mode);
}
默默吐槽一句:这react调用层级也太多了吧~ -_-||
FiberNode
fiber本质也是个构造函数,其结构如下:
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// 静态属性
this.tag = tag;
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;
// fiber树update时,所需数据
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// 副作用
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
// 优先级调度相关
this.lanes = NoLanes;
this.childLanes = NoLanes;
// 指向内存中的fiber
this.alternate = null;
}
- tag属性,在react中有25种定义
export type WorkTag =
| 0
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
| 9
| 10
| 11
| 12
| 13
| 14
| 15
| 16
| 17
| 18
| 19
| 20
| 21
| 22
| 23
| 24;
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const FundamentalComponent = 20;
export const ScopeComponent = 21;
export const Block = 22;
export const OffscreenComponent = 23;
export const LegacyHiddenComponent = 24;
-
key,即element中的key
-
elementType,一般情况就是key
-
type,一般情况和elementType一致
-
stateNode,与fiber关联的节点
-
return,父节点
-
child,第一个子节点
-
sibling,下一个兄弟节点
-
index,fiber在兄弟节点中的索引
-
ref,
这个不用多解释了吧 -
pendingProps,后来传入的props
-
memoizedProps,之前的props
-
updateQueue,更新队列
-
memoizedState,之前的state
-
dependencies,fiber节点依赖,比如context
-
mode,运行模式,比如普通模式,并发模式
-
lanes,fiber的优先级处理
-
childLanes,fiber子节点的优先级处理
-
alternate,之前的fiber
-
...
到这里,我们已经看到root的数据结构:
root = {
_internalRoot: FiberRoot,
render: () => {...},
unmount: () => {...}
}
updateContainer
react根据fiber树做渲染,涉及优先级,调和,调度等, 这里先忽略,后面章节详细再分析
三. unstable_createBlockingRoot
这种启动方式,支持了小部分并发模式功能。
createBlockingRoot
function createBlockingRoot(container, options) {
// ...校验
// ...
return new ReactDOMBlockingRoot(container, BlockingRoot, options);
}
这里和ReactDOM.render类型,只是tag模式不一样而已
ReactDOMBlockingRoot
function ReactDOMBlockingRoot(container, tag, options) {
this._internalRoot = createRootImpl(container, tag, options);
}
这里,又走到ReactDOM.render方法里了,这里的tag值是1,不是render方法中的 0。 tag将在fiber中体现,后续文章中,我们会细聊区别。
ReactDOMBlockingRoot.render
ReactDOMBlockingRoot.prototype.render = () => {
// ...
updateContainer(children, root, null, null)
}
不难看出,此种模式下,直接更新容器了
四. unstable_createRoot
createRoot
function createRoot(container, options) {
// ...校验
// ...
return new ReactDOMRoot(container, options);
}
ReactDOMRoot
function ReactDOMRoot(container: Container, options: void | RootOptions) {
this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
}
此方法,也走到了createRootImpl方法调用,此时模式为3,也就是并发模式,生成fiber流程是一致的,只是fiber的tag值不一样。
并发模式,其核心在于:可中断的异步渲染
这也是react团队,历时两年重构使用fiber的目标。并发模式也将在react18中大放异彩
五. 总结
react的启动模式有3种,分别是普通模式,小部分并发模式,并发模式。尽管3种方式启动方法调用不一样,但最终都将调用createRootImpl。 其区别在在启动阶段,区分有2。其一: fiber树tag不同,其二:render定义不同。 后两者,执行render时,直接执行updateContainer