其实Transaction这个词对我们开发并不陌生,在数据库中,事务表示的是一个原子化的操作序列,要么全部执行,要么全部不执行,是一个不可分割的工作单位。
我们可以思考一下事务的实现原理,要将多个串行的操作原子化,必然需要在出错的时候,撤销之前操作的能力,也就是需要一个现场保护和还原的机制。
而React之所以取名为Transaction,大概也就是因为在它的initialize和closeAPI中,做到了close可以拿到initialize的状态的能力,并且对抛出的异常进行比较到位的处理,它的原理如下:
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
可以看到React中实现的Transaction其实是AOP思想,对一个函数anyMethod进行切片包裹wrapper,每个wrapper可以实现自己的initialize和close接口,可以嵌套使用。
本文基于React v15.6.2版本介绍,原因请参见新手如何学习React源码
Transaction的实现位于src/renderers/utils/Transaction.js
这段代码看起来好像占了一定篇幅,其实去掉那些边边角角的try catch,这段代码核心就变成了三句话:
这三行代码也是Transaction实现的主要能力,在主体函数运行前,先运行initialize钩子,运行之后,执行close钩子。
接下来让我们关注一下实现的细节处理:
多个参数的枚举,是React源码的惯用处理手段,为什么不使用arguments我在上篇文章中已经解释过了,不做赘述。
同一时间只能有一个同类的Transaction在执行,这就是_isInTransaction控制锁的作用,也保证了事务运行过程中不被打断。
在finally的代码中可以看到,无论前面的initialize还是主体函数遇到报错,最后的close一定会执行,抛出的错误则以第一个遇到的错误为准。
接下来看一下initializeAll的实现:
可以看到initializeAll的实现,就是拿到所有的wrapper,执行其中的initialize钩子,值得注意的是,如果有钩子报错了,剩下的wrapper的钩子还是会被执行,结合上面的分析我们可以知道React这样做的原因——保持事务的原子性,有一个操作错误了,需要返回之前的现场,也就是完整的initialize和close钩子都要走一遍,以撤销之前可能已经做的操作。
closeAll的实现与initializeAll的实现类似:
这里需要注意的是,close钩子的传参来源是this.wrapperInitData,也就是上一步initialize执行的时候的返回值,这样才能够做到对现场的保护还原。
最后看一下reinitializeTransaction方法的实现:
这个方法比较简单,就是初始化操作,为什么需要这么一个方法呢?我们可以结合前面一篇对象池的文章来思考,transaction对象也是可以在对象池中复用的,那么每一次复用,都需要重置一下之前的状态,实际上在React中transaction大多也是结合对象池一起用。
了解原理之后,使用方式就很容易理解了:
用法上,构造函数默认调用reinitializeTransaction,原型继承自Transaction后,挂载getTransactionWrappers方法,然后执行perform包裹要执行的主体函数就可以了。这个时候主体函数相当于是处于一个事务中执行,会原子化地执行前置和后置函数。
React中的Transaction不多,总共就5个,但每一个都是核心中的核心:
ReactReconcileTransaction
ReactServerRendingTransaction
ReactNativeReconcileTransaction
ReactDefaultBatchingStrategyTransaction
ReactUpdatesFlushTransaction
不要小看Transaction在React中的地位,上面的ReactReconcileTransaction恰恰是componentDidMount的关键,而ReactDefaultBatchingStrategyTransaction是实现setState异步化的关键。限于篇幅,具体的transaction我们在后续的应用场景展开介绍。
React事务实现可以算是React底层的基石,虽然它只是一个utils,但是React很多非常重要的特性都是依赖于事务的。
事务的实现其实不难,可以简单理解为React仅仅是为方法加了前置和后置函数的钩子,并原子化执行函数,只有理解事务机制后,你才不会在React源码中晕头转向,因为React源码的执行顺序跟事务的钩子有极大的关联。
自此开始,我们也真正迈入了React核心实现的大门!# React源码学习入门(五)详解React中的Transaction事务机制
其实Transaction这个词对我们开发并不陌生,在数据库中,事务表示的是一个原子化的操作序列,要么全部执行,要么全部不执行,是一个不可分割的工作单位。
我们可以思考一下事务的实现原理,要将多个串行的操作原子化,必然需要在出错的时候,撤销之前操作的能力,也就是需要一个现场保护和还原的机制。
而React之所以取名为Transaction,大概也就是因为在它的initialize和closeAPI中,做到了close可以拿到initialize的状态的能力,并且对抛出的异常进行比较到位的处理,它的原理如下:
可以看到React中实现的Transaction其实是AOP思想,对一个函数anyMethod进行切片包裹wrapper,每个wrapper可以实现自己的initialize和close接口,可以嵌套使用。
本文基于React v15.6.2版本介绍,原因请参见新手如何学习React源码
Transaction的实现位于src/renderers/utils/Transaction.js
这段代码看起来好像占了一定篇幅,其实去掉那些边边角角的try catch,这段代码核心就变成了三句话:
这三行代码也是Transaction实现的主要能力,在主体函数运行前,先运行initialize钩子,运行之后,执行close钩子。
接下来让我们关注一下实现的细节处理:
多个参数的枚举,是React源码的惯用处理手段,为什么不使用arguments我在上篇文章中已经解释过了,不做赘述。
同一时间只能有一个同类的Transaction在执行,这就是_isInTransaction控制锁的作用,也保证了事务运行过程中不被打断。
在finally的代码中可以看到,无论前面的initialize还是主体函数遇到报错,最后的close一定会执行,抛出的错误则以第一个遇到的错误为准。
接下来看一下initializeAll的实现:
可以看到initializeAll的实现,就是拿到所有的wrapper,执行其中的initialize钩子,值得注意的是,如果有钩子报错了,剩下的wrapper的钩子还是会被执行,结合上面的分析我们可以知道React这样做的原因——保持事务的原子性,有一个操作错误了,需要返回之前的现场,也就是完整的initialize和close钩子都要走一遍,以撤销之前可能已经做的操作。
closeAll的实现与initializeAll的实现类似:
这里需要注意的是,close钩子的传参来源是this.wrapperInitData,也就是上一步initialize执行的时候的返回值,这样才能够做到对现场的保护还原。
最后看一下reinitializeTransaction方法的实现:
这个方法比较简单,就是初始化操作,为什么需要这么一个方法呢?我们可以结合前面一篇对象池的文章来思考,transaction对象也是可以在对象池中复用的,那么每一次复用,都需要重置一下之前的状态,实际上在React中transaction大多也是结合对象池一起用。
了解原理之后,使用方式就很容易理解了:
用法上,构造函数默认调用reinitializeTransaction,原型继承自Transaction后,挂载getTransactionWrappers方法,然后执行perform包裹要执行的主体函数就可以了。这个时候主体函数相当于是处于一个事务中执行,会原子化地执行前置和后置函数。
React中的Transaction不多,总共就5个,但每一个都是核心中的核心:
ReactReconcileTransaction
ReactServerRendingTransaction
ReactNativeReconcileTransaction
ReactDefaultBatchingStrategyTransaction
ReactUpdatesFlushTransaction
不要小看Transaction在React中的地位,上面的ReactReconcileTransaction恰恰是componentDidMount的关键,而ReactDefaultBatchingStrategyTransaction是实现setState异步化的关键。限于篇幅,具体的transaction我们在后续的应用场景展开介绍。
React事务实现可以算是React底层的基石,虽然它只是一个utils,但是React很多非常重要的特性都是依赖于事务的。
事务的实现其实不难,可以简单理解为React仅仅是为方法加了前置和后置函数的钩子,并原子化执行函数,只有理解事务机制后,你才不会在React源码中晕头转向,因为React源码的执行顺序跟事务的钩子有极大的关联。
自此开始,我们也真正迈入了React核心实现的大门!