热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

React源码分析(2):组件的初始渲染

(点击上方公众号,可快速关注)


作者:莫凡 

realtcg.com/2018/03/17/窥探React-源码分析(二)-组件的初始渲染/


上一篇文章讲到了React 调用ReactDOM.render首次渲染组件的前几个过程的源码, 包括创建元素、根据元素实例化对应组件, 利用事务来进行批量更新. 我们还穿插介绍了React 事务的实现以及如何利用事务进行批量更新的实现. 这篇文章我们接着分析后面的过程, 包括调用了哪些事务, 组件插入的过程, 组件生命周期方法什么时候被调用等.


正文


在React 源码中, 首次渲染组件有一个重要的过程, mount, 插入, 即插入到DOM中, 发生在实例化组件之后. 这是一个不断生成(render)不断插入、类似递归的过程. 让我们一步一步来分析.


使用事务执行插入过程


我们来看首先在插入之前的准备, ReactMount.js中, batchedMountComponentIntoNode被放到了批量策略batchedUpdates中执行, batchedMountComponentIntoNode 函数正是执行插入过程的第一步


// 放在批量策略batchedUpdates中执行插入

ReactUpdates.batchedUpdates(

    batchedMountComponentIntoNode,

    componentInstance,

    ...

);


这个batchingStrategy就是ReactDefaultBatchingStrategy, 因此调用了ReactDefaultBatchingStrategy的batchedUpdates, 并将batchedMountComponentIntoNode当作callback.


在ReactDefaultBatchingStrategy.js中启动了ReactDefaultBatchingStrategyTransaction事务去执行batchedMountComponentIntoNode, 以便利用策略控制更新, 而在这个函数中又启动了一个调和(Reconcile)事务, 执行mountComponentIntoNode进行插入.


// ReactDefaultBatchingStrategy.js

var transaction = new ReactDefaultBatchingStrategyTransaction();

...

var ReactDefaultBatchingStrategy = {

  ...

  batchedUpdates: function(callback, a, b, c, d, e) {

   ...

    // 启动ReactDefaultBatchingStrategy事务

      return transaction.perform(callback, null, a, b, c, d, e);

  },

};

 

// ReactMount.js

function batchedMountComponentIntoNode(

  ...

) {

  var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(

    !shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement,

  );

    // 启动Reconcile事务

  transaction.perform(

    mountComponentIntoNode,

    ...

  );

    ...

}


相信你注意到了 ReactUpdates.ReactReconcileTransaction.getPooled, 这个函数的作用就是从对象池里拿到ReactReconcileTransaction 对象重用.


React优化策略——对象池


在ReactMount.js :


function batchedMountComponentIntoNode(

  componentInstance,

  container,

  shouldReuseMarkup,

  context,

) {

    // 从对象池中拿到ReactReconcileTransaction事务

  var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(

    !shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement,

  );

    // 启动事务执行mountComponentIntoNode

  transaction.perform(

    mountComponentIntoNode,

    null,

    componentInstance,

    container,

    transaction,

    shouldReuseMarkup,

    context,

  );

    // 释放事务

  ReactUpdates.ReactReconcileTransaction.release(transaction);

}


React 在启动另一个事务之前拿到了这个事务, 从哪里拿到的呢? 这里就涉及到了React 优化策略之一——对象池


GC很慢


首先你用Javascript声明的变量不再使用时, js引擎会在某些时间回收它们, 这个回收时间是耗时的. 资料显示:


Marking latency depends on the number of live objects that have to be marked, with marking of the whole heap potentially taking more than 100 ms for large webpages.


整个堆的标记对于大型网页很可能需要超过100毫秒


尽管V8引擎对垃圾回收有优化, 但为了避免重复创建临时对象造成GC不断启动以及复用对象, React使用了对象池来复用对象, 对GC表明, 我一直在使用它们, 请不要启动回收.


React 实现的对象池其实就是对类进行了包装, 给类添加一个实例队列, 用时取, 不用时再放回, 防止重复实例化:


PooledClass.js :


// 添加对象池, 实质就是对类包装

var addPoolingTo = function (CopyConstructor, pooler) {

  // 拿到类

  var NewKlass = CopyConstructor;

  // 添加实例队列属性

  NewKlass.instancePool = [];

  // 添加拿到实例方法

  NewKlass.getPooled = pooler || DEFAULT_POOLER;

  // 实例队列默认为10个

  if (!NewKlass.poolSize) {

    NewKlass.poolSize = DEFAULT_POOL_SIZE;

  }

  // 将实例放回队列

  NewKlass.release = standardReleaser;

  return NewKlass;

};

// 从对象池申请一个实例.对于不同参数数量的类,React分别处理, 这里是一个参数的类的申请实例的方法, 其他一样

var oneArgumentPooler = function(copyFieldsFrom) {

  // this 指的就是传进来的类

  var Klass = this;

  // 如果类的实例队列有实例, 则拿出来一个

  if (Klass.instancePool.length) {

    var instance = Klass.instancePool.pop();

    Klass.call(instance, copyFieldsFrom);

    return instance;

  } else { // 否则说明是第一次实例化, new 一个

    return new Klass(copyFieldsFrom);

  }

};

// 释放实例到类的队列中

var standardReleaser = function(instance) {

  var Klass = this;

  ...

  // 调用类的解构函数

  instance.destructor();

  // 放到队列

  if (Klass.instancePool.length <Klass.poolSize) {

    Klass.instancePool.push(instance);

  }

};

 

// 使用时将类传进去即可

PooledClass.addPoolingTo(ReactReconcileTransaction);


可以看到, React对象池就是给类维护一个实例队列, 用到就pop一个, 不用就push回去. 在React源码中, 用完实例后要立即释放, 也就是申请和释放成对出现, 达到优化性能的目的.


插入过程


在ReactMount.js中, mountComponentIntoNode函数执行了组件实例的mountComponent, 不同的组件实例有自己的mountComponent方法, 做的也是不同的事情. (源码我就不上了, 太TM…)


ReactCompositeComponent类型的mountComponent方法:


React 源码分析(2):组件的初始渲染


ReactDOMComponent类型:


React 源码分析(2):组件的初始渲染


ReactDOMTextComponent类型:


React 源码分析(2):组件的初始渲染


整个mount过程是递归渲染的(矢量图):


React 源码分析(2):组件的初始渲染


刚开始, React给要渲染的组件从最顶层加了一个ReactCompositeComponent类型的 topLevelWrapper来方便的存储所有更新, 因此初次递归是从 ReactCompositeComponent 的mountComponent 开始的, 这个过程会调用组件的render函数(如果有的话), 根据render出来的elements再调用instantiateReactComponent实例化不同类型的组件, 再调用组件的 mountComponent, 因此这是一个不断渲染不断插入、递归的过程.


总结


React 初始渲染主要分为以下几个步骤:


  1. 构建一个组件的elements tree(subtree)—— 从组件嵌套的最里层(转换JSX后最里层的createElements函数)开始层层调用createElements创建这个组件elements tree. 在这个subtree中, 里层创建出来的元素作为包裹层的props.children;

  2. 实例化组件——根据当前元素的类型创建对应类型的组件实例;

  3. 利用多种事务执行组件实例的mountComponent.

    1. 首先执行topLevelWrapper(ReactCompositeComponent)的mountComponent;

    2. ReactCompositeComponent的mountComponent过程中会先调用render(Composite类型 )生成组件的elements tree, 然后顺着props.children, 不断实例化, 不断调用各自组件的mountComponent 形成循环

  4. 在以上过程中, 依靠事务进行存储更新、回调队列, 在事务结束时批量更新.



觉得本文对你有帮助?请分享给更多人

关注「Web前端开发学习圈」,提升前端技能


推荐阅读
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • Html5-Canvas实现简易的抽奖转盘效果
    本文介绍了如何使用Html5和Canvas标签来实现简易的抽奖转盘效果,同时使用了jQueryRotate.js旋转插件。文章中给出了主要的html和css代码,并展示了实现的基本效果。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 本文介绍了在使用vue和webpack进行异步组件按需加载时可能出现的报错问题,并提供了解决方法。同时还解答了关于局部注册组件和v-if指令的相关问题。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
author-avatar
疯狂一夏531
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有