经历一个月的学习整理,站在前人的肩膀上,对 React 有了一些浅薄的理解,希望记录自己的学习过程的同时也可以给大家带来一点小帮助。如果此系列文章对您有些帮助,还望在座各位义夫义母不吝点赞关注支持 ,也希望各位大佬拍砖探讨
「react」 react 基础包,只提供定义 react 组件(React Element)的必要函数,一般来说需要和渲染器(react-dom,react-native)一同使用.在编写 react 应用的代码时,大部分都是调此包的 api.
「react-dom」 react 渲染器之一,是 react 与 web 平台连接的桥梁(可以在浏览和 nodejs 环境中使用),将 react-reconciler 中的运行结果输出到 web 界面上.在编写 react 应用的代码时,大多数场景下,能用到此包的就是一个入口函数 ReactDOM.render(
,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 运行流程中的输入步骤
ReactDOM.render( , rootNode)
. 这是当前 React app 使用的方式. 这个模式可能不支持这些新功能(concurrent 支持的所有功能). // LegacyRoot
ReactDOM.render(<App />, document.getElementById('root'), dom => {}); // 支持callback回调, 参数是一个dom对象
ReactDOM.createBlockingRoot(rootNode).render( )
. 它仅提供了 concurrent 模式的小部分功能. // BlockingRoot
// 1. 创建ReactDOMRoot对象
const reactDOMBlockingRoot = ReactDOM.createBlockingRoot(
document.getElementById('root'),
);
// 2. 调用render
reactDOMBlockingRoot.render(<App />); // 不支持回调
ReactDOM.createRoot(rootNode).render( )
. 实现了时间切片等功能 // ConcurrentRoot
// 1. 创建ReactDOMRoot对象
const reactDOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
// 2. 调用render
reactDOMRoot.render(<App />); // 不支持回调
在调用入口函数之前,reactElement(
和 DOM 对象 div#root
之间没有关联
三种模式下在 react 初始化时,都会创建 3 个全局对象
ReactDOM(Blocking)Root
对象
fiberRoot
对象
HostRootFiber
对象
先放结论:
updateContainer()
函数,正是这个 updateContainer()
函数串联了 react-dom 包和 react-reconciler.因为 updateContainer()
函数中调用了 `scheduleUpdateOnFiber(xxx),进入 react 循环构造的 「唯一」入口 看到这儿是不是觉得源码好像也就这么回事(神气)
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 个步骤
ReactDOM(Blocking)
实例 updateConatiner
进行更新 Concurrent 模式和 Blocking 模式从调用方式上直接可以看出
ReactDOM.createRoot()
和 ReactDOM.createBlockingRoot()
创建 ReactDOMRoot
和 ReactDOMBlockingRoot
实例 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. 创建fiberRoot
const root = createContainer(container, tag, hydrate, hydrationCallbacks); // 注意RootTag的传递
// 2. 标记dom对象, 把dom和fiber对象关联起来
// TDOO:怎么关联起来的?
//div#root._reactRootContainer = ReactDOM(lockingRoot)._internalRoot = FiberRoot.containerInfo->div#root
markContainerAsRoot(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的updateQueue
initializeUpdateQueue(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(...)
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 包.
「不同点:」
unbatchedUpdates
, 更改执行上下文为 LegacyUnbatchedContext
, 之后调用 updateContainer
进行更新. updateContainer
进行更新. 「继续追踪 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.updateQueue
const 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 相关调度详细分析
ReferenceList:
本文由 mdnice 多平台发布