经历一个月的学习整理,站在前人的肩膀上,对 React 有了一些浅薄的理解,希望记录自己的学习过程的同时也可以给大家带来一点小帮助。如果此系列文章对您有些帮助,还望在座各位义夫义母不吝点赞关注支持 ,也希望各位大佬拍砖探讨
,doucument.getElementByID(‘root)’),其余使用的 api,基本是 react 包提供的* react- reconciler react 得以运行的核心包(综合协调 react-dom,react,scheduler 各包之前的调用与配合).管理 react 应用状态的输入和结果的输出.将输入信号最终转换成输出信号传递给渲染器* scheduler 调度机制的核心实现, 控制由 react-reconciler 送入的回调函数的执行时机, 在 concurrent 模式下可以实现任务分片. 在编写 react 应用的代码时, 同样几乎不会直接用到此包提供的 api. 核心任务就是执行回调(回调函数由 react-reconciler 提供) 通过控制回调函数的执行时机, 来达到任务分片的目的, 实现可中断渲染(concurrent 模式下才有此特性)位于 react-dom 包,衔接 reconciler 运行流程中的输入步骤
1.legacy 模式: ReactDOM.render(
. 这是当前 React app 使用的方式. 这个模式可能不支持这些新功能(concurrent 支持的所有功能).
// LegacyRoot
ReactDOM.render( , document.getElementById('root'), dom => {}); // 支持callback回调, 参数是一个dom对象
2.Blocking 模式: ReactDOM.createBlockingRoot(rootNode).render(
. 它仅提供了 concurrent 模式的小部分功能.
// BlockingRoot
// 1. 创建ReactDOMRoot对象
const reactDOMBlockingRoot = ReactDOM.createBlockingRoot(document.getElementById('root'),
);
// 2. 调用render
reactDOMBlockingRoot.render( ); // 不支持回调
3.Concurrent 模式: ReactDOM.createRoot(rootNode).render(
. 实现了时间切片等功能
// ConcurrentRoot
// 1. 创建ReactDOMRoot对象
const reactDOMRoot = ReactDOM.createRoot(document.getElementById('root'));
// 2. 调用render
reactDOMRoot.render( ); // 不支持回调
在调用入口函数之前,reactElement(
和 DOM 对象 div#root
之间没有关联
三种模式下在 react 初始化时,都会创建 3 个全局对象
1.ReactDOM(Blocking)Root
对象1.属于 react-dom 包,该对象上有 render 和 unmount 方法,通过调用 render 方法可以引导 react 应用启动
2.fiberRoot
对象1.属于 react-reconciler 包, 作为 react-reconciler 在运行过程中的全局上下文, 保存 fiber 构建过程中所依赖的全局状态.2.react 利用这些信息来循环构造 fiber 节点(后续会详细分析 fiber 的构造过程)
3.HostRootFiber
对象1.属于 react-reconciler 包, 这是 react 应用中的第一个 Fiber 对象, 是 Fiber 树的根节点, 节点的类型是 HostRoot.
先放结论:
1.三种模式都会调用 updateContainer()
函数,正是这个 updateContainer()
函数串联了 react-dom 包和 react-reconciler.因为 updateContainer()
函数中调用了 `scheduleUpdateOnFiber(xxx),进入 react 循环构造的唯一入口
2.三种模式都会创建在创建 DOMroot 中调用 createRootImpl,区分三种模式的方式只是传递的 rootTag 参数不同
看到这儿是不是觉得源码好像也就这么回事(神气)
legacy 模式
function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component,children: ReactNodeList,container: Container,forceHydrate: boolean,callback: ?Function, ) {let root: RootType = (container._reactRootContainer: any);let fiberRoot;if (!root) {// 初次调用, root还未初始化, 会进入此分支//1. 创建ReactDOMRoot对象, 初始化react应用环境root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container,forceHydrate,);fiberRoot = root._internalRoot;if (typeof callback === 'function') {const originalCallback = callback;callback = function() {// instance最终指向 children(入参: 如 ` `)生成的dom节点const instance = getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// 2. 更新容器unbatchedUpdates(() => {updateContainer(children, fiberRoot, parentComponent, callback);});} else {// root已经初始化, 二次调用render会进入// 1. 获取FiberRoot对象fiberRoot = root._internalRoot;if (typeof callback === 'function') {const originalCallback = callback;callback = function() {const instance = getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// 2. 调用更新updateContainer(children, fiberRoot, parentComponent, callback);}return getPublicRootInstance(fiberRoot);
}
继续跟踪 legacyCreateRootFromDOMContainer. 最后调用 new ReactDOMBlockingRoot(container, LegacyRoot, options)
function legacyCreateRootFromDOMContainer( container: Container,forceHydrate: boolean, ): RootType {const shouldHydrate =forceHydrate || shouldHydrateDueToLegacyHeuristic(container);return createLegacyRoot(container,shouldHydrate? {hydrate: true,}: undefined,);
}
export function createLegacyRoot( container: Container,options?: RootOptions, ): RootType {return new ReactDOMBlockingRoot(container, LegacyRoot, options); // 注意这里的LegacyRoot是固定的, 并不是外界传入的
}
function legacyCreateRootFromDOMContainer( container: Container,forceHydrate: boolean, ): RootType {const shouldHydrate =forceHydrate || shouldHydrateDueToLegacyHeuristic(container);return createLegacyRoot(container,shouldHydrate? {hydrate: true,}: undefined,);
}
export function createLegacyRoot( container: Container,options?: RootOptions, ): RootType {return new ReactDOMBlockingRoot(container, LegacyRoot, options); // 注意这里的LegacyRoot是固定的, 并不是外界传入的
}
通过上述源码追踪可得出,legacy 模式下调用 ReactDOM.render 有 2 个步骤
1.创建 ReactDOM(Blocking)
实例
2.调用 updateConatiner
进行更新
Concurrent 模式和 Blocking 模式从调用方式上直接可以看出
1.分别调用 ReactDOM.createRoot()
和 ReactDOM.createBlockingRoot()
创建 ReactDOMRoot
和 ReactDOMBlockingRoot
实例
2.调用 ReactDOMRoot
和 ReactDOMBlockingRoot
实例的 render
方法
//BlockingRoot下调用createRoot
export function createRoot( container: Container,options?: RootOptions, ): RootType {return new ReactDOMRoot(container, options);
}
//Concurrent下调用ReactDOMBlockingRoot
export function createBlockingRoot( container: Container,options?: RootOptions, ): RootType {return new ReactDOMBlockingRoot(container, BlockingRoot, options); // 注意第2个参数BlockingRoot是固定写死的
}
继续查看ReactDOMRoot和ReactDOMBlockingRoot
function ReactDOMRoot(container: Container, options: void | RootOptions) {// 创建一个fiberRoot对象, 并将其挂载到this._internalRoot之上this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
}
function ReactDOMBlockingRoot( container: Container,tag: RootTag,options: void | RootOptions, ) {// 创建一个fiberRoot对象, 并将其挂载到this._internalRoot之上this._internalRoot = createRootImpl(container, tag, options);
}
//第一个共性:调用createRootImpl创建fiberRoot对象,并将其挂载到this._internalRoot上
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);});
};
//第二个共性:原型上有个render和unmount方法,且内部都会调用updateContainer进行更新
创建 fiberRoot 对象
无论哪种模式下, 在 ReactDOM(Blocking)Root
的创建过程中, 都会调用一个相同的函数 createRootImpl()
, 查看后续的函数调用, 最后会创建 fiberRoot
对象(在这个过程中, 特别注意 RootTag 的传递过程):
// 注意: 3种模式下的tag是各不相同(分别是ConcurrentRoot,BlockingRoot,LegacyRoot).
this._internalRoot = createRootImpl(container, tag, options);
function createRootImpl( container: Container,tag: RootTag,options: void | RootOptions, ) {// ... 省略部分源码(有关hydrate服务端渲染等, 暂时用不上)// 1. 创建fiberRootconst root = createContainer(container, tag, hydrate, hydrationCallbacks); // 注意RootTag的传递// 2. 标记dom对象, 把dom和fiber对象关联起来// TDOO:怎么关联起来的?//div#root._reactRootContainer = ReactDOM(lockingRoot)._internalRoot = FiberRoot.containerInfo->div#rootmarkContainerAsRoot(root.current, container);// ...省略部分无关代码return root;
}
export function createContainer( containerInfo: Container,tag: RootTag,hydrate: boolean,hydrationCallbacks: null | SuspenseHydrationCallbacks, ): OpaqueRoot {// 创建fiberRoot对象return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks); // 注意RootTag的传递
}
export function createFiberRoot( containerInfo: any,tag: RootTag,hydrate: boolean,hydrationCallbacks: null | SuspenseHydrationCallbacks, ): FiberRoot {// 创建fiberRoot对象, 注意RootTag的传递const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);// 1. 这里创建了`react`应用的首个`fiber`对象, 称为`HostRootFiber`const uninitializedFiber = createHostRootFiber(tag);root.current = uninitializedFiber;uninitializedFiber.stateNode = root;// 2. 初始化HostRootFiber的updateQueueinitializeUpdateQueue(uninitializedFiber);return root;
}
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;}return createFiber(HostRoot, null, null, mode); // 注意这里设置的mode属性是由RootTag决定的
}
注意:fiber
树中所有节点的 mode
都会和 HostRootFiber.mode
一致(新建的 fiber 节点, 其 mode 来源于父节点),所以 HostRootFiber.mode
非常重要, 它决定了以后整个 fiber 树构建过程
以上是对legacyRenderSubtreeIntoContainer中创建的追踪,结束后会回到legacyRenderSubtreeIntoContainer中进入更新容器的函数 unbatchedUpdate(…)
1.legacy 回到 legacyRenderSubtreeIntoContainer
函数中有:
// 2. 更新容器
unbatchedUpdates(() => {updateContainer(children, fiberRoot, parentComponent, callback);
});
2.concurrent 和 blocking 在 ReactDOM(Blocking)Root
原型上有 render 方法
ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function( children: ReactNodeList, ): void {const root = this._internalRoot;// 执行更新updateContainer(children, root, null, null);
};
相同点: 3 种模式在调用更新时都会执行 updateContainer. updateContainer 函数串联了 react-dom 与 react-reconciler, 之后的逻辑进入了 react-reconciler 包.
不同点:
1.legacy 下的更新会先调用 unbatchedUpdates
, 更改执行上下文为 LegacyUnbatchedContext
, 之后调用 updateContainer
进行更新.
2.concurrent 和 blocking 不会更改执行上下文, 直接调用 updateContainer
进行更新.
3.fiber 循环构造的时候会根据执行上下文去对应不同的处理
继续追踪 updateContanier
export function updateContainer( element: ReactNodeList,container: OpaqueRoot,parentComponent: ?React$Component,callback: ?Function, ): Lane {const current = container.current;// 1. 获取当前时间戳, 计算本次更新的优先级const eventTime = requestEventTime();const lane = requestUpdateLane(current);// 2. 设置fiber.updateQueueconst update = createUpdate(eventTime, lane);update.payload = { element };//对应的dom节点callback = callback === undefined ? null : callback;if (callback !== null) {update.callback = callback;}enqueueUpdate(current, update);// 3. 进入reconciler运作流程中的`输入`环节scheduleUpdateOnFiber(current, lane, eventTime);return lane;
}
updateContainer()
函数位于 react-reconciler
包中, 它串联了 react-dom 与 react-reconciler. 需要关注其最后调用了 scheduleUpdateOnFiber
循环构造的唯一入口
关于优先级,和 updateQueue
在此处做简要的理解,React 会根据当前时间戳去计算当前任务的优先级,并创建 update 对象挂载到 updateQueue 上(环形链表)
.之后 react 运行的过程中会按任务的优先级先后执行.之后会在优先级和 react 相关调度详细分析