上一篇文章讲到了React 调用ReactDOM.render
首次渲染组件的前几个过程的源码, 包括建立元素、根据元素实例化对应组件, 利用事务来进行批量更新. 咱们还穿插介绍了React 事务的实现以及如何利用事务进行批量更新的实现. 这篇文章咱们接着分析后面的过程, 包括调用了哪些事务, 组件插入的过程, 组件生命周期方法何时被调用等.javascript
在React 源码中, 首次渲染组件有一个重要的过程, mount
, 插入, 即插入到DOM中, 发生在实例化组件以后. React使用批量策略来管理组件插入到DOM的过程. 这个“批量”不是指像遍历数组那样同批次插入, 而是一个不断生成不断插入、相似递归的过程. 让咱们一步一步来分析.java
如何管理呢? 即在插入以前就开始一次batch, 而后插入过程当中任何更新都会被enqueue, 在batchingStrategy事务的close阶段批量更新.web
咱们来看首先在插入以前的准备, ReactMount.js中, batchedMountComponentIntoNode
被放到了批量策略batchedUpdates
中执行 :数组
// 放在批量策略batchedUpdates中执行插入
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
componentInstance,
...
);
复制代码
从上篇文章展现的源码中看到, 这个batchingStrategy
就是ReactDefaultBatchingStrategy
, 所以调用了ReactDefaultBatchingStrategy
的batchedUpdates
, 并将batchedMountComponentIntoNode
看成callback.app
继续看ReactDefaultBatchingStrategy
的batchedUpdates
, 在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放进事务里执行
}
},
};
复制代码
咱们在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)事务, 以便管理插入.this
// 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,
...
);
...
}
复制代码
在ReactMount.js :google
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 优化策略之一——对象池
首先你用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 初始渲染主要分为如下几个步骤:
createElements
建立这个组件elements tree. 在这个subtree中, 里层建立出来的元素做为包裹层的props.children;mountComponent
.
mountComponent
过程当中会先调用render(Composite类型 )生成组件的elements tree, 而后顺着props.children, 不断实例化, 不断调用各自组件的mountComponent 造成循环