React-函数batchedUpdates和Transaction执行

By
写代码

【React-简单组件渲染(render)过程】里,留了两个小坑:BatchingStrategy的运行机制和transaction的运行机制。这几天抽时间继续做一些记录。

在看batchedUpdates的执行时,常常会有错觉,执行的时候有并发操作存在。后来想想,应该是里面的一些概念,比如:池(pool)、事务(transaction)。还有一些变量定义,比如:isInTransaction(是否处在事务中?如果不是有多个进程,为什么会做这个判断呢?)、isBatchingUpdates(正在批处理更新中?)等等。一定要记住:JS是单线程的。

事务(Transaction

本篇涉及一个概念–事务(Transaction),常见于数据库的并发操作里,由于js是单线程的,所以和概念上理解的事务是有区别的。

事务(Transaction),是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。

以最简单的JDBC事务(java)为例

事务中最主要的操作就是如果捕获到异常,那么就要回滚事务。而在React里用到的事务,基本逻辑也是一致的。

基础的transaction调用的perform方法都是定义在父类Transaction的。需要子类实现的方法有3个:initializeAllperformcloseAll。一目了然,执行的顺序就是

initializeAll中会依次调用transactionWrapperinitialize方法

第i个wrapper初始化遇到异常,会抛出,剩下的会继续执行。

perform这么写

如果遇到初始化异常,会导致函数执行的时候跳过ret = method.call,直接走到finally处理,执行closeAll收尾。而执行method过程中遇到异常,也是一样的逻辑。

简单组件渲染涉及的两个transaction结构如下图
react-transaction

这里父子类关系使用了继承,JS中怎么实现继承?让一个对象有另一个对象的属性和方法就行了,常用方式是加在原型(prototype)上,React里由_assign(current, Parent)实现。

batchedUpdates代码流程

注意这里transactionReactDefaultBatchingStrategyTransaction,定义如下

1、可能翻batchingStrategy或者ReactUpdates代码的时候,看不到相关的赋值操作,这是因为这个ReactDefaultBatchingStrategyTransaction和下面的ReactReconcileTransaction,都是通过初始化ReactDOM的时候,注入(inject)进去的。这样做的目的是减小函数的耦合,方便以后更改策略,相关说明请看篇尾。

2、在React里,batchingStrategy是以单例存在的。这就意味着对于组件的渲染和更新操作,都会通过同一个实例进行。这样做的目的是为类似setState等操作创建一个唯一的“操作环境”,避免不必要的更新。这里的一个关键标志位就是
batchingStrategy.isBatchingUpdates

后续文章可以看到,如setState方法触发的组件update都会通过isBatchingUpdates这个判断。

3、初次渲染时,传入的callbackbatchedMountComponentIntoNode,这样就接到了之前文章的最后,后面具体的HTML-dom渲染过程先不看。

ReactReconcileTransaction的调用过程

相关概念及实现

依赖注入

简要画个调用关系
react-injection

本来,按照顺序执行,应该由ReactUpdates来主动发起调用,比如使用ReactReconcileTransaction。这里控制权转给了ReactDOM,由它初始化的时候进行注入。这样做只需仿照ReactDefaultInjection.js的结构,引入不同的策略,就可以进行不同的注入,而不必修改ReactUpdates等具体文件。

池(Pool)

ReactReconcileTransaction使用了Pool,即“池”的概念。除了对象本身,还使用了CallbackQueue这个已提前实例化好的对象,用来跟踪对象的update(这个目前未涉及,先不看)。

对于池化的对象,使用方式是PooledClass.getPooled()来获取实例。

使用池,可以
1、节省创建类的实例的开销;
2、节省创建类的实例的时间;
3、防止存储空间随着对象的增多而增大。

React中使用的PooledClass是静态池,由于方法和属性都是直接添加到原对象上的,所以PooledClass中所有方法中的this均指向调用的对象,而非池实例。

使用PooledClass.addPoolingTo(targetObj)给原对象添加

之后,即可在原对象上使用getPooledrelease等方法来操作。

池化对象的使用步骤:
1)第一次,使用getPooled来从池中获取一个实例,instancePool的长度为0,所以会直接返回一个新的实例;
2)在实例上进行目标操作;
3)释放池实例,调用实例的destructor,并将实例放入instancePool中;
4)第二次,会直接从instancePoolpop一个实例,以重复使用。

后记

不管用什么语言实现,软件设计的思路和技巧是一致的。尤其规模化的框架,脱离不了经典的数据结构和设计模式。看其他类型语言的实现方法,思考对比,没坏处。

1

Comments: 0

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

:razz: