IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> react启动过程 -> 正文阅读

[JavaScript知识库]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 模式下才有此特性)

    alt

react 应用的启动过程

位于 react-dom 包,衔接 reconciler 运行流程中的输入步骤

启动模式

  1. legacy 模式: ReactDOM.render(<App />, rootNode). 这是当前 React app 使用的方式. 这个模式可能不支持这些新功能(concurrent 支持的所有功能).
//?LegacyRoot
ReactDOM.render(<App?/>,?document.getElementById('root'),?dom?=>?{});?//?支持callback回调,?参数是一个dom对象
  1. Blocking 模式: ReactDOM.createBlockingRoot(rootNode).render(<App />). 它仅提供了 concurrent 模式的小部分功能.
//?BlockingRoot
//?1.?创建ReactDOMRoot对象
const?reactDOMBlockingRoot?=?ReactDOM.createBlockingRoot(
??document.getElementById('root'),
);
//?2.?调用render
reactDOMBlockingRoot.render(<App?/>);?//?不支持回调
  1. 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是固定写死的
}

继续查看「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(...)

  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<any,?any>,
??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 相关调度详细分析

「更多系列文章会首发同名 VX 公众号」

ReferenceList:

  1. https://github.com/7kms/react-illustration-series
  2. https://react.iamkasong.com/preparation/idea.html#%E6%80%BB%E7%BB%9
  3. https://juejin.cn/post/7085145274200358949

本文由 mdnice 多平台发布

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-05 23:27:51  更:2022-07-05 23:29:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/11 11:57:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码