一,什么是React中的事务
在数据库中,事务表示的是一个原子化的操作序列,要么全部执行,要么全部不执行,是一个不可分割的工作单位。
事务的实现原理,要将多个串行的操作原子化,必然需要在出错的时候,撤销之前操作的能力,也就是需要一个现场保护和还原的机制。
在 React 中,事务是指一组更新操作,这些操作通常是针对组件的状态或 DOM 的属性的更改。在事务的完成前,这些操作都被视为一个整体,要么金部完成,要么全部回滚。React 采用的是乐观更新策略,也就是先进行一次更新,然后再检测是否需要回滚。如果需要回滚,则会抛出一个异常,从而让 React 自动进行回滚操作。
而React之所以取名为Transaction,大概也就是因为在它的initialize和closeAPI 中,做到了close可以拿到initialize的状态的能力,并且对抛出的异常进行比较到位的处理。
二,Transaction详解
其实在react中transaction的本质,其实算是一种设计模式,它的思路其实很像AOP切面编程:
给目标函数添加一系列的前置和后置函数,对目标函数进行功能增强或者代码环境保护。
其源碼在 React/lib/Transaction.js。
Transaction 就是給須要執行的方法fn用 wrapper 封裝了 initialize 和 close 方法。且支持多次封裝。再经过Transaction 提供的 perform 方法執行。 perform执行前,调用全部initialize 方法。perform 方法执行后,调用全部close方法。
在React执行点击事件或者生命周期函数时,会使用一个Transaction对象将整个执行过程包裹成一个事务。
关于事务机制的设计,React源代码中给出了一份草图描述其工作原理,如下所示:
* 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中用事务执行方法,就是用wrapper(称之为套套吧)把方法包裹起来- 然后每个
wapper中都提供一个initialize方法和一个close方法 - 当需要使用事务调用一个方法,例如上图中的
anyMethod时,使用事务提供的perform方法,将需要执行的方法传入, - 这个时候就会按顺序执行
wrapper.initalize,anyMethod,wrapper.close - 而且,事务还支持多个事务的嵌套,当执行方法被多个
wapper包裹时,事务会先按顺序执行所有的initalize方法,再执行anyMethod,最后按顺序执行所有的close函数
例如上图就表示会按以下顺序执行
wrapper1.initalize,
wrapper2.initalize,
anyMethod,
wrapper1.close,
wrapper2.close
简单的来说,Transaction 会接受一个方法 func,和一组 Wrapper。Transaction会在 func 执行之前,执行一组Wrapper中的initialize方法。而后执行 func 方法,在 func 方法执行完了之后,执行Wrapper 提供的所有 close 方法。
在 Transcation 对象中整个执行效果有些类似我们平时做的单元测试。
分为初始化,执行和清理。
这样就可以在我们所有生命周期函数和点击事件中的 setState 方法调用前设置一个环境—— isBatchingUpdates。
三,总结
React 的更新是基于 Transaction(事务)的,Transacation 就是给目标执行的函数包裹一下,加上前置和后置的 hook (有点类似 koa 的 middleware),在开始执行之前先执行 initialize hook,结束之后再执行 close hook,这样搭配上 isBatchingUpdates 这样的布尔标志位就可以实现一整个函数调用栈内的多次 setState 全部入 pending 队列,结束后统一 apply 了。
但是 setTimeout 这样的方法执行是脱离了事务的,react 管控不到,所以就没法 batch 了。