ReactDOM.render在react源码中执行之后发生了什么?

news2024/11/27 22:35:25

ReactDOM.render

通常是如下图使用,在提供的 container 里渲染一个 React 元素,并返回对该组件的引用(或者针对无状态组件返回 null)。本文主要是将ReactDOM.render的执行流程在后续文章中会对创建更新的细节进行分析,文中的源代码部分为了方便阅读将__DEV__部分的代码移除掉了。

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

render

位于:react-dom/src/client/ReactDOMLegacy.js

export function render(
  element: React$Element<any>,  container: Container,  callback: ?Function,
) {
  // 验证container是否为有效的DOM节点
  invariant(
    isValidContainer(container),
    'Target container is not a DOM element.',
  );
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}

返回了一个legacyRenderSubtreeIntoContainer函数,这里注意有5个参数

parentComponent: 父组件因为是初次创建所以为null。

children: 传入的ReactElement

container: 渲染React的DOM容器

forceHydrate: 判断是否需要协调,在服务端渲染的情况下已渲染的DOM结构是类似的因此可以在对比后进行复用。在服务端渲染的情况下使用ReactDOM.hydrate()与 render() 相同只是forceHydrate会标记为true。

callback: 渲染完成后的回调函数

legacyRenderSubtreeIntoContainer

位于:react-dom/src/client/ReactDOMLegacy.js
作用:

  • 判断是否为初次渲染,如果是就创建root并将root._internalRoot赋值给fiberRoot同时封装callback回调,然后调用unbatchedUpdates立即更新子节点。
  • 如果不是第一次渲染则进入正常的updateContainer流程。
  • 最后getPublicRootInstance(fiberRoot)返回公开的 Root 实例对象。
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function,
) {

  // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  // member of intersection type." Whyyyyyy.
  let root: RootType = (container._reactRootContainer: any);
  let fiberRoot;
  if (!root) {
    // Initial mount 初次渲染创建FiberRoot
    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);
      };
    }
    // Initial mount should not be batched.
    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);
      };
    }
    // Update
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}

legacyCreateRootFromDOMContainer

位于:react-dom/src/client/ReactDOMLegacy.js
初次渲染进入创建root的环节:root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate)
作用:主要是判断是否为服务端渲染,如果是的话就会复用存在的dom节点进行协调(reconciliation)提高性能,如果不是则会清空container中的子元素,最后传入container和shouldHydrate返回createLegacyRoot函数。

function legacyCreateRootFromDOMContainer(
  container: Container,  forceHydrate: boolean,
): RootType {
  const shouldHydrate =
    forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // 判断是否是服务端渲染
  // First clear any existing content.
  if (!shouldHydrate) {
    let warned = false;
    let rootSibling;
    while ((rootSibling = container.lastChild)) {
      container.removeChild(rootSibling);
    }
  }

  return createLegacyRoot(
    container,
    shouldHydrate
      ? {
          hydrate: true,
        }
      : undefined,
  );
}

createLegacyRoot

位于:react-dom/src/client/ReactDOMRoot.js
作用:返回了一个ReactDOMBlockingRoot实例,这里传入了LegacyRoot是一个常量=0代表着现在使用的同步渲染模式,是为了后续的Concurrent可中断渲染模式做准备。

export function createLegacyRoot(  container: Container,  options?: RootOptions, // hydrate
): RootType {
  return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}

ReactDOMBlockingRoot

位于:react-dom/src/client/ReactDOMRoot.js
作用:将createRootImpl函数的返回(FiberRoot)挂载到实例的_internalRoot上

function ReactDOMBlockingRoot(
  container: Container,  tag: RootTag,  options: void | RootOptions,
) {
  this._internalRoot = createRootImpl(container, tag, options);
}

相关参考视频讲解:进入学习

createRootImpl

位于:react-dom/src/client/ReactDOMRoot.js
作用:执行createContainer拿到FiberRootNode并赋值给root,再通过markContainerAsRoot将RootFiber挂载到container上。

function createRootImpl(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
) {
  // Tag is either LegacyRoot or Concurrent Root
  const hydrate = options != null && options.hydrate === true;
  const hydrationCallbacks =
    (options != null && options.hydrationOptions) || null;
    // 拿到FiberRootNode
  const root = createContainer(container, tag, hydrate, hydrationCallbacks);
  // 将FiberRootNode挂载到container
  markContainerAsRoot(root.current, container);
  if (hydrate && tag !== LegacyRoot) {
    const doc =
      container.nodeType === DOCUMENT_NODE
        ? container
        : container.ownerDocument;
    eagerlyTrapReplayableEvents(container, doc);
  }
  return root;
}

createContainer

位于:react-reconciler/src/ReactFiberReconciler.old.js 作用:返回createFiberRoot

export function createContainer(  containerInfo: Container,  tag: RootTag,  hydrate: boolean,  hydrationCallbacks: null | SuspenseHydrationCallbacks,): OpaqueRoot {
  return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}

createFiberRoot

位于:react-reconciler/src/react-reconciler/src/ReactFiberReconciler.old.js 作用: 新建FiberRoot对象并赋值给root,初始化Fiber(通常叫做RootFiber)通过root.current = uninitializedFiber和uninitializedFiber.stateNode = root将两者联系起来。
执行initializeUpdateQueue(uninitializedFiber)创建一个更新队列,挂载fiber.updateQueue下面
最后将root返回

export function createFiberRoot(  containerInfo: any,  tag: RootTag,  hydrate: boolean,  hydrationCallbacks: null | SuspenseHydrationCallbacks,): FiberRoot {
  // 新建fiberRoot对象
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
  if (enableSuspenseCallback) {
    root.hydrationCallbacks = hydrationCallbacks;
  }

  // Cyclic construction. This cheats the type system right now because
  // stateNode is any.
  //初始化RootFiber
  const uninitializedFiber = createHostRootFiber(tag);
  root.current = uninitializedFiber;
  // RootFiber的stateNode指向FiberRoot
  uninitializedFiber.stateNode = root;

  initializeUpdateQueue(uninitializedFiber);

  return root;
}

FiberRoot RootFiber 和 updateQueue

ReactDOM.render主要创建了三个对象FiberRooat、RootFiber和Updatequeue下面我们这对这三个对象进行分析

FiberRoot

FiberRoot是FiberRootNode(containerInfo, tag, hydrate)的实例
位于:react-reconciler/src/ReactFiberRoot/FiberRootNode
作用:

  • 整个应用的起点
  • 包含应用挂载的目标节点
  • 记录整个应用更新过程的各种信息
function FiberRootNode(containerInfo, tag, hydrate) {
  // 标记不同的组件类型
  this.tag = tag;
  // 当前应用对应的Fiber对象,是Root Fiber
  // current:Fiber对象 对应的是 root 节点,即整个应用根对象
  this.current = null;
  // root节点,render方法接收的第二个参数
  this.containerInfo = containerInfo;
   // 只有在持久更新中会用到,也就是不支持增量更新的平台,react-dom不会用到
  this.pendingChildren = null;
  this.pingCache = null;

  //任务有三种,优先级有高低:
  //(1)没有提交的任务
  //(2)没有提交的被挂起的任务
  //(3)没有提交的可能被挂起的任务

   //当前更新对应的过期时间
  this.finishedExpirationTime = NoWork;
  //已经完成任务的FiberRoot对象,如果你只有一个Root,那么该对象就是这个Root对应的Fiber或null
  //在commit(提交)阶段只会处理该值对应的任务
  this.finishedWork = null;
  // 在任务被挂起的时候通过setTimeout设置的返回内容,用来下一次如果有新的任务挂起时清理还没触发的timeout(例如suspense返回的promise)
  this.timeoutHandle = noTimeout;
  // 顶层context对象,只有主动调用renderSubTreeIntoContainer时才会被调用
  this.context = null;
  this.pendingContext = null;
  // 第一次渲染是否需要调和
  this.hydrate = hydrate;
  // Node returned by Scheduler.scheduleCallback
  this.callbackNode = null;
  this.callbackPriority = NoPriority;
  //存在root中,最旧的挂起时间
  //不确定是否挂起的状态(所有任务一开始均是该状态)
  this.firstPendingTime = NoWork;
  this.firstSuspendedTime = NoWork;
  this.lastSuspendedTime = NoWork;
  this.nextKnownPendingLevel = NoWork;
  //存在root中,最新的挂起时间
  //不确定是否挂起的状态(所有任务一开始均是该状态)
  this.lastPingedTime = NoWork;
  this.lastExpiredTime = NoWork;
  this.mutableSourcePendingUpdateTime = NoWork;

  if (enableSchedulerTracing) {
    this.interactionThreadID = unstable_getThreadID();
    this.memoizedInteractions = new Set();
    this.pendingInteractionMap = new Map();
  }
  if (enableSuspenseCallback) {
    this.hydrationCallbacks = null;
  }
}

RootFiber

RootFiber初始化于const uninitializedFiber = createHostRootFiber(tag) 通过 createFiber 返回 FiberNode的实例 作用:

  • 每个ReactElement对应一个Fiber对象
  • 记录节点的各种状态(方便了hooks,因为记录state和props都是在Fiber只是完成后再挂载到this的例如:pendingProps pendingState memoizedProps memoizedState)
  • 串联整个应用形成树结构
// 位于 react-reconciler/src/ReactFiber.js
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);
}

const createFiber = function(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
): Fiber {
  return new FiberNode(tag, pendingProps, key, mode);
};

// FiberNode结构
function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  // 标记不同的组件类型
  this.tag = tag;
  // ReactElement里面的key
  this.key = key;
  // ReactElement.type,也就是我们调用`createElement`的第一个参数
  this.elementType = null;
  // 异步组件lazy component resolved之后返回的内容,一般是`function`或者`class`组件
  this.type = null;
  // 对应节点的实例,比如类组件就是class的实例,如果是dom组件就是dom实例,如果是function component就没有实例这里为空
  this.stateNode = null;

  // Fiber Fiber是个链表通过child和Sibling连接,遍历的时候先遍历child如果没有子元素了则访问return回到上级查询是否有sibling
  // 指向他在Fiber节点树中的‘parent’,用来在处理完这个节点之后向上返回
  this.return = null;
  // 指向第一个子节点
  this.child = null;
  // 指向自己的兄弟节点,兄弟节点的return指向同一个副节点
  this.sibling = null;
  this.index = 0;

  this.ref = null;
  // 新的变动带来的新的props
  this.pendingProps = pendingProps;
  // 上次渲染完成后的props
  this.memoizedProps = null;
  // 该Fiber对应的组件产生的update会存放在这个队列(比如setState和forceUpdate创建的更新)
  this.updateQueue = null;
  // 上一次的state
  this.memoizedState = null;

  this.dependencies = null;
  // 
  this.mode = mode;

  // Effects
  // 用来记录副作用
  this.effectTag = NoEffect;
  // 单链表用来快速查找下一个side effect
  this.nextEffect = null;
  // 子树中第一个side effect
  this.firstEffect = null;
  // 子树中最后一个side effect
  this.lastEffect = null;

  // 代表任务在未来的哪个时候应该被完成 就是过期时间
  // 不包括他的子树产生的任务
  this.expirationTime = NoWork;
  // 快速确定子树中是否有不再等待的变化
  this.childExpirationTime = NoWork;

  // Fiber树更新过程中,每个FIber都会有一个跟其对应的Fiber
  // 我们称他为`current <==> workInProgress`
  // 渲染完成后他们会交换位置
  this.alternate = null;

  // 调试相关的去掉了

}

updateQueue

initializeUpdateQueue(uninitializedFiber);
位于:react-reconciler/src/ReactUpdateQueue.js
作用:单向链表,用来存放update,next来串联update
关于Update和UpdateQueue涉及到的东西比较多打算单独一章来讲解

export function initializeUpdateQueue<State>(fiber: Fiber): void {
  const queue: UpdateQueue<State> = {
    // 每次操作完更新阿之后的state
    baseState: fiber.memoizedState,
    // 队列中的第一个`Update`
    firstBaseUpdate: null,
    // 队列中的最后一个`Update`
    lastBaseUpdate: null,
    shared: {
      pending: null,
    },
    effects: null,
  };
  fiber.updateQueue = queue;
}

流程图

最后是画的大致流程图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/143000.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB-plot3/ezplot3三维绘图

&#xff08;1&#xff09; plot3是三维绘图的基本函数&#xff0c;调用格式如下。1、plot3( X,Y,Z):绘制简单的三维曲线&#xff0c;当X、Y、Z是长度相同的向量时&#xff0c;plot3命令将绘制以向量X、Y、Z为(x, y,z)坐标值的三维曲线;当X、Y、Z是mn矩阵时,plot3命令将绘制m条…

Android 虚拟分区详解(四) 编译开关

Android Virtual A/B 系统简称 VAB,我将其称为虚拟分区。 本系列文章基于 Android R(11) 进行分析,如果没有特别说明,均基于代码版本 android-11.0.0_r46 请已经购买《Android 虚拟分区》专栏的朋友加我 wx 进 "虚拟分区专栏 VIP 答疑"群,作为本专栏文章的附加服…

(6)元对象系统与信号与槽机制

1. 元对象系统 元对象系统是一个基于标准C的扩展&#xff0c;为Qt提供了信号与槽机制、实时类型信息、动态属性系统。 什么是元对象 在计算机科学中&#xff0c;元对象是这样一个东西&#xff1a;它可以操纵、创建、描述、或执行其他对象。元对象描述的对象称为基对象。元对象可…

记一次搭建备库,使用连接串主库无法连接到备库

主库使用连接串连接备库失败 SQL> conn sys/oracleorcldg as sysdba ERROR: ORA-12528: TNS:listener: all appropriate instances are blocking new connections 备库已经建立了静态监听 # listener.ora Network Configuration File: /u01/app/oracle/product/11.2.0/db_1/…

安全寒假第一堂课

一、状态码 200 – 服务器成功返回网页 404 – 请求的网页不存在 503 – 服务器超时 1xx&#xff08;临时响应&#xff09; 表示临时响应并需要请求者继续执行操作的状态码。 100&#xff08;继续&#xff09; 请求者应当继续提出请求。服务器返回此代码表示已收到请求的第一…

OpenCV实战(5)——图像运算详解

OpenCV实战&#xff08;5&#xff09;——图像运算详解0. 前言1. 图像基本运算2. 重载图像运算符2.1 加法运算符重载2.2 分割图像通道2.3 完整代码3. 图像重映射3.1 OpenCV 重映射函数3.2 完整代码小结系列链接0. 前言 图像可以以不同的方式进行组合&#xff0c;因为它们是正则…

XGBoost论文阅读

XGBoost: A Scalable Tree Boosting System 目录 XGBoost: A Scalable Tree Boosting System 1.摘要 2.方法 2.1 正则化学习目标 2.2 梯度提升树 2.3 收缩率和列采样 2.4分裂点查找算法 1.摘要 提出了一种新的稀疏性感知算法&#xff0c;用于稀疏数据和加权全图草图&a…

Python教程:什么是三级模式和二级映像?

美国国家标准学会(American National Standards Institute,ANSI)所属的标准计划与需求委员会&#xff08;Standards Planning and Requirements Committee,SPARC)在1971年公布的研究报告中提出了ANSI-SPARC体系结构&#xff0c;即三级模式结构&#xff08;或称为三层体系结构&a…

ArcGIS基础实验操作100例--实验53导出线、面要素的坐标值

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验53 导出线、面要素的坐标值 目录 一、实验背景 二、实验数据 三、实验步骤 &#xf…

笔记杂项(一)

都是踩过的坑&#xff0c;趟过的水。 ubuntu虚拟机终端字体太小的设置方法&#xff1a;ubuntu18.04调整终端字体大小 这个方法试试看&#xff1a;https://zhuanlan.zhihu.com/p/139305626 容器里面编译内核代码&#xff0c;进程被杀掉的原因是触发了内核OOM killer&#xff0c…

干货| app自动化测试之Andriod微信小程序的自动化测试

随着微信小程序的功能和生态日益完善&#xff0c;很多公司的小程序项目页面结构越来越多&#xff0c;业务逻辑也越来越复杂。如何做好小程序的自动化测试就成为测试同学普遍面临的一大痛点难题。微信小程序小程序内嵌于微信内部&#xff0c;页面包含 Native 原生元素和 Web 元素…

华为防火墙与二层交换机对接配置VLAN上网设置

拓扑图 一、防火墙设置 1、G1/0/0接口设置IP&#xff0c;G1/0/1接口切换二层口设置VLAN&#xff0c;G1/0/0 桥接了本地无线网卡来模拟公网地址 <USG6000V1>sys [USG6000V1]sys FW1 [FW1]un in en# 设置公网IP [FW1]int g1/0/0 [FW1-GigabitEthernet1/0/0]ip addr 192.1…

package.json配置解读之入门

文章目录前言一、描述配置nameversionrepositorydescriptionkeywordslicenseauthor二、文件配置filestypemainbrowsermoduleexportsworkspaces三、脚本配置scriptsconfig四、结语前言 package.json是每个前端项目都会有的json文件&#xff0c;位于项目的根目录中。很多脚手架在…

RHCE(chrony服务器)

chrony服务器 chrony服务器是一个开源自由的网络时间协议NTP的客户端和服务器的软件&#xff0c;他能让计算机保持系统时钟和时钟服务器保持同步&#xff0c;让计算机保持精确的时间&#xff0c;chrony也可以作为服务端软件为其他计算机提供时间同步服务 chrony由两部分组成&…

openAI--十拳剑助你做AI时代的弄潮儿

AI它厉害&#xff08;diao&#xff09;吗&#xff1f; 最近大家玩chatgpt还好吗&#xff1f; 有被它的恋爱情商暴击到吗&#xff1f; 有没有觉得那在leetcode上所向无敌的技巧都是浮云吗&#xff1f; 今天&#xff0c;我为大家带来十个很好的AI平台。这一篇先介绍一下&…

【远程桌面】nomachine下载安装使用教程、zerotier下载安装使用教程超详细

文章目录一、软件介绍二、NoMachine远程桌面1.Windows下载安装使用2.Linux下载安装使用3.Android下载安装使用4.ARM下载安装使用&#xff08;未实践&#xff09;三、ZeroTier内网穿透0.官网注册账户1.Windows下载安装使用2.Linux下载安装使用3.Android下载安装使用4.ARM下载安装…

Android 学习 - 不完善

SharedPreference 共享参数用法 SharedPreference 是 Android 的一个轻量级存储工具, 采用的存储结构是Key-Value的键值对方式. 共享参数的存储介质是符合XML规范的配置文件. 保存路径是: /data/data/应用包名/shared_prefs/文件名.xml 利用元数据配置快捷菜单 (1)元数据的met…

【阶段二】Python数据分析Pandas工具使用11篇:探索性数据分析:数据的检验:卡方检验与t检验

本篇的思维导图: 探索性数据分析:数据的检验 卡方检验 在实际的学习或工作中,也会碰到关于离散型变量之间的探索性分析,如两个离散变量之间是否相互独立。对于该问题的解答,就需要运用统计学中的卡方检验了。卡方检验属于非参数的检验方法,其原假设是两个离散变…

Spring——最全Spring目录

&#x1f4eb;作者简介&#xff1a;zhz小白 公众号&#xff1a;小白的Java进阶之路 专业技能&#xff1a; 1、Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理 2、熟悉Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理&#xff0c;具备⼀定的线…

2022 IoTDB Summit:IoTDB PMC侯昊男《Apache IoTDB首创时序顺乱序分离存储引擎 IoTLSM》...

12 月 3 日、4日&#xff0c;2022 Apache IoTDB 物联网生态大会在线上圆满落幕。大会上发布 Apache IoTDB 的分布式 1.0 版本&#xff0c;并分享 Apache IoTDB 实现的数据管理技术与物联网场景实践案例&#xff0c;深入探讨了 Apache IoTDB 与物联网企业如何共建活跃生态&#…