React中的Transaction事务机制

496 阅读3分钟

一,什么是React中的事务

在数据库中,事务表示的是一个原子化的操作序列,要么全部执行,要么全部不执行,是一个不可分割的工作单位。

事务的实现原理,要将多个串行的操作原子化,必然需要在出错的时候,撤销之前操作的能力,也就是需要一个现场保护和还原的机制。

在 React 中,事务是指一组更新操作,这些操作通常是针对组件的状态或 DOM 的属性的更改。在事务的完成前,这些操作都被视为一个整体,要么金部完成,要么全部回滚。React 采用的是乐观更新策略,也就是先进行一次更新,然后再检测是否需要回滚。如果需要回滚,则会抛出一个异常,从而让 React 自动进行回滚操作。

而React之所以取名为Transaction,大概也就是因为在它的initializecloseAPI 中,做到了close可以拿到initialize的状态的能力,并且对抛出的异常进行比较到位的处理。

二,Transaction详解

其实在reacttransaction的本质,其实算是一种设计模式,它的思路其实很像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可以实现自己的initializeclose接口,可以嵌套使用。

根据这幅图,我们可以这样理解事务:

  1. react 中用事务执行方法,就是用 wrapper(称之为套套吧)把方法包裹起来
  2. 然后每个 wapper 中都提供一个 initialize 方法和一个 close 方法
  3. 当需要使用事务调用一个方法,例如上图中的 anyMethod 时,使用事务提供的 perform 方法,将需要执行的方法传入,
  4. 这个时候就会按顺序执行wrapper.initalize, anyMethod, wrapper.close
  5. 而且,事务还支持多个事务的嵌套,当执行方法被多个 wapper 包裹时,事务会先按顺序执行所有的 initalize 方法,再执行 anyMethod ,最后按顺序执行所有的 close 函数

例如上图就表示会按以下顺序执行

wrapper1.initalize,
wrapper2.initalize,
anyMethod,
wrapper1.close,
wrapper2.close

简单的来说,Transaction 会接受一个方法 func,和一组 WrapperTransaction会在 func 执行之前,执行一组Wrapper中的initialize方法。而后执行 func 方法,在 func 方法执行完了之后,执行Wrapper 提供的所有 close 方法。

Transcation 对象中整个执行效果有些类似我们平时做的单元测试。

分为初始化,执行和清理。

这样就可以在我们所有生命周期函数和点击事件中的 setState 方法调用前设置一个环境—— isBatchingUpdates

image.png

三,总结

React 的更新是基于 Transaction(事务)的,Transacation 就是给目标执行的函数包裹一下,加上前置和后置的 hook (有点类似 koa 的 middleware),在开始执行之前先执行 initialize hook,结束之后再执行 close hook,这样搭配上 isBatchingUpdates 这样的布尔标志位就可以实现一整个函数调用栈内的多次 setState 全部入 pending 队列,结束后统一 apply 了。 但是 setTimeout 这样的方法执行是脱离了事务的,react 管控不到,所以就没法 batch 了。