博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
窥探React-源码分析(二)
阅读量:5832 次
发布时间:2019-06-18

本文共 6926 字,大约阅读时间需要 23 分钟。

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

正文

在React 源码中, 首次渲染组件有一个重要的过程, mount, 插入, 即插入到DOM中, 发生在实例化组件之后. React使用批量策略来管理组件插入到DOM的过程. 这个“批量”不是指像遍历数组那样同批次插入, 而是一个不断生成不断插入、类似递归的过程. 让我们一步一步来分析.

使用批量策略管理插入

如何管理呢? 即在插入之前就开始一次batch, 然后插入过程中任何更新都会被enqueue, 在batchingStrategy事务的close阶段批量更新.

启动策略

我们来看首先在插入之前的准备, ReactMount.js中, batchedMountComponentIntoNode被放到了批量策略batchedUpdates中执行 :

// 放在批量策略batchedUpdates中执行插入ReactUpdates.batchedUpdates(    batchedMountComponentIntoNode,    componentInstance,    ...);复制代码

从上篇文章展示的源码中看到, 这个batchingStrategy就是ReactDefaultBatchingStrategy, 因此调用了ReactDefaultBatchingStrategybatchedUpdates, 并将batchedMountComponentIntoNode当作callback.

执行策略

继续看ReactDefaultBatchingStrategybatchedUpdates, 在ReactDefaultBatchingStrategy.js :

// 批处理策略var ReactDefaultBatchingStrategy = {  isBatchingUpdates: false, // 是否处在一次BatchingUpdates标志位  // 批量更新策略调用的就是这个方法  batchedUpdates: function(callback, a, b, c, d, e) {    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;	// 一旦调用批处理, 重置isBatchingUpdates标志位, 表示正处在一次BatchingUpdates中    ReactDefaultBatchingStrategy.isBatchingUpdates = true;    // 首次插入时, 由于是第一次启动批量策略, 因此alreadyBatchingUpdates为false, 执行事务    if (alreadyBatchingUpdates) {      return callback(a, b, c, d, e);    } else {      return transaction.perform(callback, null, a, b, c, d, e);  // 将callback放进事务里执行    }  },};复制代码

在执行插入的过程中enqueue更新

我们在componentWillMount里setState, 看看React会怎么做:

// ReactBaseClasses.js :ReactComponent.prototype.setState = function(partialState, callback) {  this.updater.enqueueSetState(this, partialState);  if (callback) {    this.updater.enqueueCallback(this, callback, 'setState');  }};//ReactUpdateQueue.js:enqueueSetState: function(publicInstance, partialState) {	// enqueueUpdate    var queue =      internalInstance._pendingStateQueue ||      (internalInstance._pendingStateQueue = []);    queue.push(partialState);    enqueueUpdate(internalInstance); }//ReactUpdate.js:function enqueueUpdate(component) {  ensureInjected(); // 注入默认策略        // 如果不是在一次batch就开启一次batch  if (!batchingStrategy.isBatchingUpdates) {    batchingStrategy.batchedUpdates(enqueueUpdate, component);    return;  }    // 如果是就存储更新  dirtyComponents.push(component);  if (component._updateBatchNumber == null) {    component._updateBatchNumber = updateBatchNumber + 1;  }}复制代码

批量更新

在ReactUpdates.js中

var flushBatchedUpdates = function () {  // 批量处理dirtyComponents  while (dirtyComponents.length || asapEnqueued) {    if (dirtyComponents.length) {      var transaction = ReactUpdatesFlushTransaction.getPooled();      transaction.perform(runBatchedUpdates, null, transaction);      ReactUpdatesFlushTransaction.release(transaction);    }// 批量处理callback    if (asapEnqueued) {      asapEnqueued = false;      var queue = asapCallbackQueue;      asapCallbackQueue = CallbackQueue.getPooled();      queue.notifyAll();      CallbackQueue.release(queue);    }  }};复制代码

使用事务执行插入过程

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

// ReactDefaultBatchingStrategy.jsvar 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.jsfunction batchedMountComponentIntoNode(  ...) {  var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(    !shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement,  );    // 启动Reconcile事务  transaction.perform(    mountComponentIntoNode,    ...  );    ...}复制代码

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方法:

ReactDOMComponent类型:

ReactDOMTextComponent类型:

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

刚开始, 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. 在以上过程中, 依靠事务进行存储更新、回调队列, 在事务结束时批量更新.

转载地址:http://zcrdx.baihongyu.com/

你可能感兴趣的文章
TBOOX开通微信公众号
查看>>
apache反向代理,web,tomcat集群,weblogic集群
查看>>
8月第1周中国.COM域名总量增4万个 美国增近3万个
查看>>
php 随机字母数字
查看>>
Linux JDK6 中文乱码问题
查看>>
Oracle RAC如何避免脑裂
查看>>
震惊:2/3 被黑的网站隐藏着后门
查看>>
我的友情链接
查看>>
阿里云应用高可用服务公测发布
查看>>
linux服务器启动报错error 21 :selected disk does not exist pressy any key contunle
查看>>
jetty 3.9.0和 run-jetty-run1.3.3.2 eclipse插件的使用区别
查看>>
Centos7下IBM WAS Liberty轻量化中间件安装部署教程
查看>>
SSH Secure File Transfer Client用public key认证登录
查看>>
基于run-jetty-run插件对maven web项目的热部署
查看>>
webbench 装配指南
查看>>
视频管理软件技术分析报告(四)--基于SOA的VMS软件架构设计
查看>>
Spring:四种配置bean的方式以及父bean和子bean
查看>>
安全管理类软件技术发展趋势
查看>>
linux 学习的第二篇记录
查看>>
UITableViewCellEditStyle多功能的实现
查看>>