React源码分析(二)
上一篇文章讲到了React 调用 正文在React 源码中,首次渲染组件有一个重要的过程, 使用批量策略管理插入如何管理呢? 即在插入之前就开始一次batch,然后插入过程中任何更新都会被enqueue,在batchingStrategy事务的close阶段批量更新. 启动策略我们来看首先在插入之前的准备,ReactMount.js中, // 放在批量策略batchedUpdates中执行插入 ReactUpdates.batchedUpdates( batchedMountComponentIntoNode,componentInstance,... ); 从上篇文章展示的源码中看到,这个 执行策略继续看 // 批处理策略 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,e); } else { return transaction.perform(callback,null,e); // 将callback放进事务里执行 } },}; 在执行插入的过程中enqueue更新我们在 // 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,transaction); ReactUpdatesFlushTransaction.release(transaction); } // 批量处理callback if (asapEnqueued) { asapEnqueued = false; var queue = asapCallbackQueue; asapCallbackQueue = CallbackQueue.getPooled(); queue.notifyAll(); CallbackQueue.release(queue); } } }; 使用事务执行插入过程
// ReactDefaultBatchingStrategy.js var transaction = new ReactDefaultBatchingStrategyTransaction(); ... var ReactDefaultBatchingStrategy = { ... batchedUpdates: function(callback,e) { ... // 启动ReactDefaultBatchingStrategy事务 return transaction.perform(callback,e); },}; // ReactMount.js function 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,transaction,); // 释放事务 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中, ReactCompositeComponent类型的mountComponent方法:
ReactDOMComponent类型:
ReactDOMTextComponent类型:
整个mount过程是递归渲染的(矢量图):
刚开始,React给要渲染的组件从最顶层加了一个ReactCompositeComponent类型的 topLevelWrapper来方便的存储所有更新,因此初次递归是从 ReactCompositeComponent 的 总结React 初始渲染主要分为以下几个步骤:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |