在【React-简单组件渲染(render)过程】里,留了两个小坑:BatchingStrategy的运行机制和transaction的运行机制。这几天抽时间继续做一些记录。
在看batchedUpdates
的执行时,常常会有错觉,执行的时候有并发操作存在。后来想想,应该是里面的一些概念,比如:池(pool)、事务(transaction
)。还有一些变量定义,比如:isInTransaction
(是否处在事务中?如果不是有多个进程,为什么会做这个判断呢?)、isBatchingUpdates
(正在批处理更新中?)等等。一定要记住:JS是单线程的。
事务(Transaction
)
本篇涉及一个概念–事务(Transaction
),常见于数据库的并发操作里,由于js是单线程的,所以和概念上理解的事务是有区别的。
事务(Transaction),是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。
以最简单的JDBC事务(java)为例
public void JdbcTransfer() {
java.sql.Connection conn = null;
try{
conn = conn =DriverManager.getConnection("xxx@xxx","username","userpwd");
action1();
action2();
action3();
// 提交事务
conn.commit();
} catch(SQLException e){
try{
// 发生异常,回滚在本事务中的操作
conn.rollback();
conn.close();
}catch(Exception ignore){
}
e.printStackTrace();
}
}
事务中最主要的操作就是如果捕获到异常,那么就要回滚事务。而在React里用到的事务,基本逻辑也是一致的。
基础的transaction
调用的perform
方法都是定义在父类Transaction
的。需要子类实现的方法有3个:initializeAll
、perform
、closeAll
。一目了然,执行的顺序就是
-this.initializeAll(0); // start:0 -callback.call(a, b, c, d, e, f); -this.closeAll(0); // start:0
initializeAll
中会依次调用transactionWrapper
的initialize
方法
for (var i = startIndex; i < transactionWrappers.length; i++) { try { this.wrapperInitData[i] = wrapper.initialize } finally { // 如果wrapper.initialize抛出异常 { // 静默执行initializeAll(i+1) // } } }
第i个wrapper初始化遇到异常,会抛出,剩下的会继续执行。
perform这么写
try { // ... this.initializeAll ret = method.call // ... } finally { // ... closeAll }
如果遇到初始化异常,会导致函数执行的时候跳过ret = method.call
,直接走到finally
处理,执行closeAll
收尾。而执行method过程中遇到异常,也是一样的逻辑。
简单组件渲染涉及的两个transaction
结构如下图
这里父子类关系使用了继承,JS中怎么实现继承?让一个对象有另一个对象的属性和方法就行了,常用方式是加在原型(prototype
)上,React
里由_assign(current, Parent)
实现。
batchedUpdates
代码流程
ReactUpdates.batchedUpdates( batchedMountComponentIntoNode, // targetFunction componentInstance, // ReactCompositeComponentWrapper container, // HTML DOM div#container shouldReuseMarkup, context );
=batchingStrategy.batchedUpdates(callback, a, b, c, d, e); batchingStrategy.isBatchingUpdates = true; =transaction.perform(callback, null, a, b, c, d, e); // Transaction.perform
注意这里transaction
为ReactDefaultBatchingStrategyTransaction
,定义如下
transaction = new ReactDefaultBatchingStrategyTransaction();
1、可能翻batchingStrategy
或者ReactUpdates
代码的时候,看不到相关的赋值操作,这是因为这个ReactDefaultBatchingStrategyTransaction
和下面的ReactReconcileTransaction
,都是通过初始化ReactDOM
的时候,注入(inject
)进去的。这样做的目的是减小函数的耦合,方便以后更改策略,相关说明请看篇尾。
2、在React
里,batchingStrategy
是以单例存在的。这就意味着对于组件的渲染和更新操作,都会通过同一个实例进行。这样做的目的是为类似setState
等操作创建一个唯一的“操作环境”,避免不必要的更新。这里的一个关键标志位就是
batchingStrategy.isBatchingUpdates
。
后续文章可以看到,如setState
方法触发的组件update都会通过isBatchingUpdates
这个判断。
3、初次渲染时,传入的callback
即batchedMountComponentIntoNode
,这样就接到了之前文章的最后,后面具体的HTML-dom渲染过程先不看。
ReactReconcileTransaction的调用过程
-transaction.perform( // Transaction.perform mountComponentIntoNode, null, componentInstance, container, transaction, // 当前的transaction shouldReuseMarkup, context ); -this.initializeAll(0); // start:0 -callback.call(a, b, c, d, e, f); // 开始执行mountComponentIntoNode -this.closeAll(0); // start:0
-ReactUpdates.ReactReconcileTransaction.release(transaction); // ReactReconcileTransaction
相关概念及实现
依赖注入
var ReactDefaultInjection = require('ReactDefaultInjection'); ReactDefaultInjection.inject();
简要画个调用关系
本来,按照顺序执行,应该由ReactUpdates
来主动发起调用,比如使用ReactReconcileTransaction
。这里控制权转给了ReactDOM
,由它初始化的时候进行注入。这样做只需仿照ReactDefaultInjection.js
的结构,引入不同的策略,就可以进行不同的注入,而不必修改ReactUpdates
等具体文件。
池(Pool)
ReactReconcileTransaction
使用了Pool,即“池”的概念。除了对象本身,还使用了CallbackQueue
这个已提前实例化好的对象,用来跟踪对象的update(这个目前未涉及,先不看)。
对于池化的对象,使用方式是PooledClass.getPooled()来获取实例。
使用池,可以
1、节省创建类的实例的开销;
2、节省创建类的实例的时间;
3、防止存储空间随着对象的增多而增大。
React
中使用的PooledClass
是静态池,由于方法和属性都是直接添加到原对象上的,所以PooledClass
中所有方法中的this
均指向调用的对象,而非池实例。
使用PooledClass.addPoolingTo(targetObj)
给原对象添加
instancePool, // 存放池实例,默认为空数组[] getPooled, // 获取池方法,默认为oneArgumentPooler poolSize, // 池大小,默认为10 release // 池释放方法
之后,即可在原对象上使用getPooled
、release
等方法来操作。
池化对象的使用步骤:
1)第一次,使用getPooled来从池中获取一个实例,instancePool
的长度为0,所以会直接返回一个新的实例;
2)在实例上进行目标操作;
3)释放池实例,调用实例的destructor
,并将实例放入instancePool
中;
4)第二次,会直接从instancePool
中pop
一个实例,以重复使用。
后记
不管用什么语言实现,软件设计的思路和技巧是一致的。尤其规模化的框架,脱离不了经典的数据结构和设计模式。看其他类型语言的实现方法,思考对比,没坏处。