ReactDOM.render源码解析-1

FileSystem
• 阅读 3029
初步看了react-dom这个包的一些源码,发现其比react包要复杂得多,react包中基本不存在跨包调用的情况,他所做的也仅仅是定义了ReactElement对象,封装了对ReactElement的基本操作,而react-dom包存在复杂的函数调用。本文将对ReactDOM.render源码做一个初步解析。
文章中如有不当之处,欢迎交流指点。react版本16.8.2。在源码添加的注释在githubreact-source-learn

前言

使用react时常常写类似下面的代码:

import ReactDOM from 'react-dom';

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);

代码1

这里导入的ReactDOM就是packages/react-dom/client/ReactDOM.js中所导出的对象。从文档可见ReactDOM对象有如下几个方法:(ps:从源码看其实还有很多其他方法)

  • render()
  • hydrate()
  • unmountComponentAtNode()
  • findDOMNode()
  • createPortal()

本文只介绍render()方法

代码分析

render方法定义如下:

   render(
    element: React$Element<any>,
    container: DOMContainer,
    callback: ?Function,
  ) {
    invariant(
      // 1
      isValidContainer(container),
      'Target container is not a DOM element.',
    );
    if (__DEV__) {
      warningWithoutStack(
        !container._reactHasBeenPassedToCreateRootDEV,
        'You are calling ReactDOM.render() on a container that was previously ' +
          'passed to ReactDOM.%s(). This is not supported. ' +
          'Did you mean to call root.render(element)?',
        enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
      );
    }
    // 2
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback,
    );
  },

代码2

render方法接收两个必选参数可一个可选参数,结合代码1的调用可知,element是一个ReactElement对象, container是一个dom节点,callback在上面的代码1并没有指定,他是一个可选函数。

这个render方法做的事情比较简单,一是校验container参数,二是调用legacyRenderSubtreeIntoContainer方法并返回。

接下来是legacyRenderSubtreeIntoContainer

// 删除了第一次调ReactDOM.render不会走的分支
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>, // ReactDOM.render 是null
  children: ReactNodeList, // 是一个ReactElement , ReactDOM.render是第一个参数
  container: DOMContainer, // 是一个dom节点, ReactDOM.render是第二个参数
  forceHydrate: boolean, // ReactDOM.render 是false
  callback: ?Function, // ReactDOM.render 是 第三个参数
) {
  if (__DEV__) {
    topLevelUpdateWarnings(container);
  }

  // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  // member of intersection type." Whyyyyyy.
  // 根据type知道, Root type是个对象,包含
  // render方法
  // unmount方法
  // legacy_renderSubtreeIntoContainer 方法
  // createBatch 方法
  // _internalRoot属性
  let root: Root = (container._reactRootContainer: any);
  if (!root) { // ReactDOM.render调用时走这里
      // Initial mount
      // 调用 legacyCreateRootFromDOMContainer 拿 Root
      root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate, // ReactDOM.render是false
    );

    // 在callback加参数
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(root._internalRoot);
        originalCallback.call(instance);
      };
    }
    // Initial mount should not be batched.
    // 这个是packages/react-reconciler/ReactFiberScheduler.js中的方法
    // TOLEARN: 这个里边应该是一些调度过程, 后续再看
    unbatchedUpdates(() => {
      if (parentComponent != null) {
        root.legacy_renderSubtreeIntoContainer(
          parentComponent,
          children,
          callback,
        );
      } else { // ReactDOM.render方法走这里
        // 这里的root.render 返回的是一个叫Work的东西, TOLEARN,这个Work后面再做了解
        root.render(children, callback);
      }
    });
  }
  // getPublicRootInstance是/packages/react-reconciler/ReactFiberReconciler.js中的方法
  // 关于他是返回的一个什么东西, 后面再看, 总之他是我ReactDOM.render方法回调函数的一个参数,
  // 也是返回值
  return getPublicRootInstance(root._internalRoot);
}

legacyRenderSubtreeIntoContainer在第一次render时做了如下事情:

  1. 调用legacyCreateRootFromDOMContainer拿到一个ReactRoot的实例
  2. 在callback中注入一个参数instance
  3. 调用unbatchedUpdates开始一轮调度过程,这个是猜的
  4. 返回instance

关于instance的获取是调用的getPublicRootInstance,getPublicRootInstance是/packages/react-reconciler/ReactFiberReconciler.js中的方法,后面再研究

关于unbatchedUpdates, 这个东西是这个是packages/react-reconciler/ReactFiberScheduler.js中的方法,简单看了看还没太明白

接下来看一下ReactRoot实例的获取

// 删除了__DEV__分支的代码
// 若需要清理container的子节点,清理, 然后new ReactRoot并返回
function legacyCreateRootFromDOMContainer(
  container: DOMContainer,  // dom节点
  forceHydrate: boolean, // false render
): Root {
  // 是否不需要清理container的子元素, 第一次render是false, 即需要清理
  const shouldHydrate =
    forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // 第一次调用时为false
  // First clear any existing content.
  if (!shouldHydrate) { // 第一次render走这里
    let warned = false;
    let rootSibling;
    // 这里将container的子元素都清理掉了
    while ((rootSibling = container.lastChild)) {
      container.removeChild(rootSibling);
    }
  }
  // Legacy roots are not async by default.
  const isConcurrent = false;
  return new ReactRoot(container, isConcurrent, shouldHydrate);
}

这个方法比较简单,在第一次调用ReactDOM.render时,shouldHydrate会是false,所以会走到if (!shouldHydrate) 分支里,将container节点的所有子节点都清理掉,最后是new 了一个ReactRoot作为返回值,关于ReactRoot等内容将放到后面的文章分析。

小结

ReactDOM.render源码解析-1

以上是ReactDOM.render的函数调用示意图。

  • 首先在render方法中校验container参数是否合法,然后调用legacyRenderSubtreeIntoContainer
  • 在legacyRenderSubtreeIntoContainer中, 调用legacyCreateRootFromDOMContainer拿到了ReactRoot的一个实例,调用getPublicRootInstance拿到了instance,用于注入到callback,和作为返回值,调用unbatchedUpdates开始调度过程
  • 在legacyCreateRootFromDOMContainer中,首先清理了container中的所有子节点,然后new了一个ReactRoot并返回

TODO

  • unbatchedUpdates是如何调度的
  • ReactRoot是一个什么样的类
  • dom的操作是在哪里
点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
菜园前端 菜园前端
2年前
一篇文章教会你什么是闭包
原文链接:什么是闭包?闭包的概念并不复杂,但是它的定义比较绕(就像平时经常用到它,却又说不出来是什么)。可以在一个作用域中调用函数的内部函数并访问到该函数中的作用域的成员,这就是闭包。给一个建议,网上闭包的概念可以搜出来一大堆,但是你真的了解它吗?你有去调
Easter79 Easter79
3年前
sync.Once
今天阅读go部分源码的时候发现了一个包sync.Once那么这个包来干什么的呢?通过百度和查看源码得知sync.Once可以控制函数只能被调用一次。不能多次重复调用。varconfOncesync.OnceconfOnce.Do(func(){log.Println("test")})
CuterCorley CuterCorley
4年前
商业数据分析从入门到入职(8)Python模块、文件IO和面向对象
前言本文先介绍了Python中程序、模块和包的基本使用,并在此基础上介绍了Python标准库。然后详细介绍了Python中的文件IO操作,包括文本文件、二进制文件的读写和其他IO操作。最后介绍了面向对象,包括类的定义、继承的使用、鸭子类型和魔法方法。一、程序、模块和包1.自定义模块和包之前我们使用的.ipynb文件都不是纯Python文件,
Karen110 Karen110
4年前
​一篇文章总结一下Python库中关于时间的常见操作
前言本次来总结一下关于Python时间的相关操作,有一个有趣的问题。如果你的业务用不到时间相关的操作,你的业务基本上会一直用不到。但是如果你的业务一旦用到了时间操作,你就会发现,淦,到处都是时间操作。。。所以思来想去,还是总结一下吧,本次会采用类型注解方式。time包importtime时间戳从1970年1月1日00:00:00标准时区诞生到现在
Stella981 Stella981
3年前
React应用渲染界面的入口
jsx代码:varReactrequire('react');varReactDOMrequire('reactdom');varMyButtonControllerrequire('./components/MyButtonController');ReactDOM.render
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
JavaWeb 调用接口
JavaWeb 如何调用接口CreateTime2018年4月2日19:04:29Author:Marydon1.所需jar包!(https://oscimg.oschina.net/oscnet/0f139
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
FileSystem
FileSystem
Lv1
回忆总是会打我一个巴掌指着旧的伤疤不准我遗忘
文章
3
粉丝
0
获赞
0