React源码系列之React启动过程分析

React源码系列之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-dom 包,衔接 reconciler 运行流程中的输入步骤

启动模式

1.legacy 模式: ReactDOM.render(<App />, rootNode). 这是当前 React app 使用的方式. 这个模式可能不支持这些新功能(concurrent 支持的所有功能).

// LegacyRoot
ReactDOM.render(<App />, document.getElementById('root'), dom => {}); // 支持callback回调, 参数是一个dom对象 

2.Blocking 模式: ReactDOM.createBlockingRoot(rootNode).render(<App />). 它仅提供了 concurrent 模式的小部分功能.

// BlockingRoot
// 1. 创建ReactDOMRoot对象
const reactDOMBlockingRoot = ReactDOM.createBlockingRoot(document.getElementById('root'),
);
// 2. 调用render
reactDOMBlockingRoot.render(<App />); // 不支持回调 

3.Concurrent 模式: ReactDOM.createRoot(rootNode).render(<App />). 实现了时间切片等功能

// ConcurrentRoot
// 1. 创建ReactDOMRoot对象
const reactDOMRoot = ReactDOM.createRoot(document.getElementById('root'));
// 2. 调用render
reactDOMRoot.render(<App />); // 不支持回调 

启动流程

在调用入口函数之前,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.

创建 ReactDOM(Blocking)Root 对象

先放结论:

1.三种模式都会调用 updateContainer()函数,正是这个 updateContainer()函数串联了 react-dom 包和 react-reconciler.因为 updateContainer()函数中调用了 `scheduleUpdateOnFiber(xxx),进入 react 循环构造的唯一入口
2.三种模式都会创建在创建 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还未初始化, 会进入此分支//1. 创建ReactDOMRoot对象, 初始化react应用环境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);};}// 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 模式

Concurrent 模式和 Blocking 模式从调用方式上直接可以看出

1.分别调用 ReactDOM.createRoot()ReactDOM.createBlockingRoot() 创建 ReactDOMRootReactDOMBlockingRoot 实例
2.调用 ReactDOMRootReactDOMBlockingRoot 实例的 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是固定写死的
} 

继续查看ReactDOMRootReactDOMBlockingRoot

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.concurrentblocking 不会更改执行上下文, 直接调用 updateContainer 进行更新.
3.fiber 循环构造的时候会根据执行上下文去对应不同的处理

继续追踪 updateContanier

export function updateContainer( element: ReactNodeList,container: OpaqueRoot,parentComponent: ?React$Component<any, any>,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 相关调度详细分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值