React 启动过程分析
经历一个月的学习整理,站在前人的肩膀上,对 React 有了一些浅薄的理解,希望记录自己的学习过程的同时也可以给大家带来一点小帮助。如果此系列文章对您有些帮助,还望在座各位义夫义母不吝点赞关注支持 🐶,也希望各位大佬拍砖探讨
React 包概览
-
「react」 react 基础包,只提供定义 react 组件(React Element)的必要函数,一般来说需要和渲染器(react-dom,react-native)一同使用.在编写 react 应用的代码时,大部分都是调此包的 api.
-
「react-dom」 react 渲染器之一,是 react 与 web 平台连接的桥梁(可以在浏览和 nodejs 环境中使用),将 react-reconciler 中的运行结果输出到 web 界面上.在编写 react 应用的代码时,大多数场景下,能用到此包的就是一个入口函数 ReactDOM.render(<APP/>
,doucument.getElementByID('root)'),其余使用的 api,基本是 react 包提供的
-
「react- reconciler」 react 得以运行的核心包(综合协调 react-dom,react,scheduler 各包之前的调用与配合).管理 react 应用状态的输入和结果的输出.将输入信号最终转换成输出信号传递给渲染器
-
「scheduler」 调度机制的核心实现, 控制由 react-reconciler 送入的回调函数的执行时机, 在 concurrent 模式下可以实现任务分片. 在编写 react 应用的代码时, 同样几乎不会直接用到此包提供的 api. 核心任务就是执行回调(回调函数由 react-reconciler 提供) 通过控制回调函数的执行时机, 来达到任务分片的目的, 实现可中断渲染(concurrent 模式下才有此特性)
react 应用的启动过程
位于 react-dom 包,衔接 reconciler 运行流程中的输入步骤
启动模式
-
legacy 模式:
ReactDOM.render(<App />, rootNode)
. 这是当前 React app 使用的方式. 这个模式可能不支持这些新功能(concurrent 支持的所有功能).
ReactDOM.render(<App?/>,?document.getElementById('root'),?dom?=>?{});?
-
Blocking 模式:
ReactDOM.createBlockingRoot(rootNode).render(<App />)
. 它仅提供了 concurrent 模式的小部分功能.
const?reactDOMBlockingRoot?=?ReactDOM.createBlockingRoot(
??document.getElementById('root'),
);
reactDOMBlockingRoot.render(<App?/>);?
-
Concurrent 模式:
ReactDOM.createRoot(rootNode).render(<App />)
. 实现了时间切片等功能
const?reactDOMRoot?=?ReactDOM.createRoot(
??document.getElementById('root')
??);
reactDOMRoot.render(<App?/>);?
启动流程
在调用入口函数之前,reactElement(
和 DOM 对象 div#root
之间没有关联
创建全局对象
三种模式下在 react 初始化时,都会创建 3 个全局对象
-
ReactDOM(Blocking)Root
对象
-
属于 react-dom 包,该对象上有 render 和 unmount 方法,通过调用 render 方法可以引导 react 应用启动
-
fiberRoot
对象
-
属于 react-reconciler 包, 作为 react-reconciler 在运行过程中的全局上下文, 保存 fiber 构建过程中所依赖的全局状态.
-
react 利用这些信息来循环构造 fiber 节点(后续会详细分析 fiber 的构造过程)
-
HostRootFiber
对象
-
属于 react-reconciler 包, 这是 react 应用中的第一个 Fiber 对象, 是 Fiber 树的根节点, 节点的类型是 HostRoot.
创建 ReactDOM(Blocking)Root 对象
先放结论:
-
三种模式都会调用
updateContainer()
函数,正是这个
updateContainer()
函数串联了 react-dom 包和 react-reconciler.因为
updateContainer()
函数中调用了 `scheduleUpdateOnFiber(xxx),进入 react 循环构造的
「唯一」入口
-
三种模式都会创建在创建 DOMroot 中调用 createRootImpl,区分三种模式的方式只是传递的 rootTag 参数不同
看到这儿是不是觉得源码好像也就这么回事(神气)
legacy 模式
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)?{
????
????
????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?{
????
????
????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. 最后调用 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);?
}
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);?
}
通过上述源码追踪可得出,legacy 模式下调用 ReactDOM.render 有 2 个步骤
-
-
Concurrent 模式和 Blocking 模式
Concurrent 模式和 Blocking 模式从调用方式上直接可以看出
-
分别调用
ReactDOM.createRoot()
和
ReactDOM.createBlockingRoot()
创建
ReactDOMRoot
和
ReactDOMBlockingRoot
实例
-
调用
ReactDOMRoot
和
ReactDOMBlockingRoot
实例的
render
方法
export?function?createRoot(
??container:?Container,
??options?:?RootOptions,
):?RootType?{
??return?new?ReactDOMRoot(container,?options);
}
export?function?createBlockingRoot(
??container:?Container,
??options?:?RootOptions,
):?RootType?{
??return?new?ReactDOMBlockingRoot(container,?BlockingRoot,?options);?
}
继续查看「ReactDOMRoot」和「ReactDOMBlockingRoot」
function?ReactDOMRoot(container:?Container,?options:?void?|?RootOptions)?{
??
??this._internalRoot?=?createRootImpl(container,?ConcurrentRoot,?options);
}
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);
??});
};
「创建 fiberRoot 对象」
无论哪种模式下, 在 ReactDOM(Blocking)Root
的创建过程中, 都会调用一个相同的函数 createRootImpl()
, 查看后续的函数调用, 最后会创建 fiberRoot
对象(在这个过程中, 特别注意 RootTag 的传递过程):
this._internalRoot?=?createRootImpl(container,?tag,?options);
function?createRootImpl(
??container:?Container,
??tag:?RootTag,
??options:?void?|?RootOptions,
)?{
??
??
??const?root?=?createContainer(container,?tag,?hydrate,?hydrationCallbacks);?
??
??
??
??markContainerAsRoot(root.current,?container);
??
??return?root;
}
export?function?createContainer(
??containerInfo:?Container,
??tag:?RootTag,
??hydrate:?boolean,
??hydrationCallbacks:?null?|?SuspenseHydrationCallbacks,
):?OpaqueRoot?{
??
??return?createFiberRoot(containerInfo,?tag,?hydrate,?hydrationCallbacks);?
}
export?function?createFiberRoot(
??containerInfo:?any,
??tag:?RootTag,
??hydrate:?boolean,
??hydrationCallbacks:?null?|?SuspenseHydrationCallbacks,
):?FiberRoot?{
??
??const?root:?FiberRoot?=?(new?FiberRootNode(containerInfo,?tag,?hydrate):?any);
??
??const?uninitializedFiber?=?createHostRootFiber(tag);
??root.current?=?uninitializedFiber;
??uninitializedFiber.stateNode?=?root;
??
??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);?
}
注意:fiber
树中所有节点的 mode
都会和 HostRootFiber.mode
一致(新建的 fiber 节点, 其 mode 来源于父节点),所以 HostRootFiber.mode
非常重要, 它决定了以后整个 fiber 树构建过程
进入更新入口
以上是对「legacyRenderSubtreeIntoContainer」中创建的追踪,结束后会回到「legacyRenderSubtreeIntoContainer」中进入更新容器的函数 unbatchedUpdate(...)
-
legacy 回到
legacyRenderSubtreeIntoContainer
函数中有:
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 包.
「不同点:」
-
「legacy」 下的更新会先调用
unbatchedUpdates
, 更改执行上下文为
LegacyUnbatchedContext
, 之后调用
updateContainer
进行更新.
-
「concurrent」 和
「blocking」 不会更改执行上下文, 直接调用
updateContainer
进行更新.
-
fiber 循环构造的时候会根据执行上下文去对应不同的处理
「继续追踪 updateContanier」
export?function?updateContainer(
??element:?ReactNodeList,
??container:?OpaqueRoot,
??parentComponent:??React$Component<any,?any>,
??callback:??Function,
):?Lane?{
??const?current?=?container.current;
??
??const?eventTime?=?requestEventTime();
??const?lane?=?requestUpdateLane(current);
??
??const?update?=?createUpdate(eventTime,?lane);
??update.payload?=?{?element?};
??callback?=?callback?===?undefined???null?:?callback;
??if?(callback?!==?null)?{
????update.callback?=?callback;
??}
??enqueueUpdate(current,?update);
??
??scheduleUpdateOnFiber(current,?lane,?eventTime);
??return?lane;
}
updateContainer()
函数位于 react-reconciler
包中, 它串联了 react-dom 与 react-reconciler. 需要关注其最后调用了 scheduleUpdateOnFiber
「循环构造的唯一入口」
关于优先级,和 updateQueue
在此处做简要的理解,React 会根据当前时间戳去计算当前任务的优先级,并创建 update 对象挂载到 updateQueue 上(环形链表)
.之后 react 运行的过程中会按任务的优先级先后执行.之后会在优先级和 react 相关调度详细分析
「更多系列文章会首发同名 VX 公众号」
ReferenceList:
-
https://github.com/7kms/react-illustration-series
-
https://react.iamkasong.com/preparation/idea.html#%E6%80%BB%E7%BB%9
-
https://juejin.cn/post/7085145274200358949