React Transaction

简介

事务机制在react中是一个非常重要的概念,它可以将诸多方法调用包裹起来集中执行。事务这个概念在数据库操作过程中非常常见,事务具有原子性这个特性,简单说,一个事务中若干任务是作为一个整体执行,要么全部执行成功,要么其中一个失败导致全部失败然后撤回修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* `Transaction` creates a black box that is able to wrap any method such that
* certain invariants are maintained before and after the method is invoked
* (Even if an exception is thrown while invoking the wrapped method). Whoever
* instantiates a transaction can provide enforcers of the invariants at
* creation time. The `Transaction` class itself will supply one additional
* automatic invariant for you - the invariant that any transaction instance
* should not be run while it is already being run. You would typically create a
* single instance of a `Transaction` for reuse multiple times, that potentially
* is used to wrap several different methods. Wrappers are extremely simple -
* they only require implementing two methods.
*
* invariant词汇的理解释义参考: http://wulfric.me/2017/11/what-is-invariant/
* `Transaction`创建一个黑盒,这样他可以包裹任何方法,以达到在执行这个方法前后维护某些确定的约束(
* 即使这个方法抛出了错误)的目标。无论如何,实例化一个事务可以在在创建时提供约束的实施方(需求方)。
* Transaction Class本身提供额外的绝对约束条件给你-任何事务实例的不变量不应该在它已经运行时运行。
* 你一般会创建一个Transaction单例来重用多次,它可能被用来包装不同的方法。Wrappers是非常简单的,
* 它仅仅要求继承initialize&&close这两个方法。

* Use cases:
* - Preserving the input selection ranges before/after reconciliation.
* Restoring selection even in the event of an unexpected error.
* - Deactivating events while rearranging the DOM, preventing blurs/focuses,
* while guaranteeing that afterwards, the event system is reactivated.
* - Flushing a queue of collected DOM mutations to the main UI thread after a
* reconciliation takes place in a worker thread.
* - Invoking any collected `componentDidUpdate` callbacks after rendering new
* content.
* - (Future use case): Wrapping particular flushes of the `ReactWorker` queue
* to preserve the `scrollTop` (an automatic scroll aware DOM).
* - (Future use case): Layout calculations before and after DOM updates.
*
* 用例:
* - 在执行前后保存输入框选择范围(即鼠标选中一个文字高亮区块),即使过程中出错也可以返回这个选择范围
* - 页面重绘前解除事件绑定,防止鼠标焦点进出,当完成了页面重绘恢复事件绑定
* - 在工作线程中进行协调后,将收集的DOM突变队列刷新到主UI线程(批量更新)。
* - render新的内容后 批量执行componentDidUpdate中收集到的回调
* - (未来用例) 保留scrollTop
* - (未来用例) 布局计算
*/

在React事务这里的注释里面对使用场景已经有相对详细的描述了。所以后面主要是看看事务的实例和使用。

使用场景

这里暂时跳过服务端和ReactNative端的调用。主要的使用在以下三个文件中。

ReactReconcileTransaction.js

调度模块这里使用到了事务,注册了三个事务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var SELECTION_RESTORATION = {
initialize: ReactInputSelection.getSelectionInformation,
close: ReactInputSelection.restoreSelection,
};

var EVENT_SUPPRESSION = {
initialize: function() {
var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
ReactBrowserEventEmitter.setEnabled(false);
return currentlyEnabled;
},
close: function(previouslyEnabled) {
ReactBrowserEventEmitter.setEnabled(previouslyEnabled);
},
};

var ON_DOM_READY_QUEUEING = {
initialize: function() {
this.reactMountReady.reset();
},
close: function() {
this.reactMountReady.notifyAll();
},
};

SELECTION_RESTORATION这个事务还是比较好理解,用来保存input的选择范围&刷新后重新选中这个返回。

这里不妨通过这个来看看这个过程中事务的数据流是如何运行的,尤其是,这个数据保存在哪儿,又如何恢复的。

这里需要对事务逻辑中等初始化和结束事务逻辑进行分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
initializeAll: function (startIndex) {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
try {
this.wrapperInitData[i] = OBSERVED_ERROR;
this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
} finally {
if (this.wrapperInitData[i] === OBSERVED_ERROR) {
try {
this.initializeAll(i + 1);
} catch (err) {}
}
}
}
},
closeAll: function (startIndex) {
!this.isInTransaction() ? "development" !== 'production' ? invariant(false, 'Transaction.closeAll(): Cannot close transaction when none are open.') : _prodInvariant('28') : void 0;
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
var errorThrown;
try {
errorThrown = true;
if (initData !== OBSERVED_ERROR && wrapper.close) {
wrapper.close.call(this, initData);
}
errorThrown = false;
} finally {
if (errorThrown) {
try {
this.closeAll(i + 1);
} catch (e) {}
}
}
}
this.wrapperInitData.length = 0;
}

可以看到,当事务开始初始化时候会将数据保存在this.wrapperInitData中,这是一个数组,数据的索引对应着事务索引,当事务结束时候将数据从this.wrapperInitData拿到并还原,一切完毕之后执行this.wrapperInitData.length = 0将其数据清空。

事务简况:

事务名称作用
SELECTION_RESTORATIONinput textarea选择状态保存和恢复
EVENT_SUPPRESSIONDOM上事件的卸载和重新激活
ON_DOM_READY_QUEUEING

ReactDefaultBatchingStrategy.js

1
2
3
4
5
6
7
8
9
10
11
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};

var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
事务名称作用
RESET_BATCHED_UPDATES仅在事务结束后将isBatchingUpdates重置为false
FLUSH_BATCHED_UPDATES批量更新标记的dirtyComponents数组

ReactUpdates.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var NESTED_UPDATES = {
initialize: function() {
this.dirtyComponentsLength = dirtyComponents.length;
},
close: function() {
if (this.dirtyComponentsLength !== dirtyComponents.length) {
dirtyComponents.splice(0, this.dirtyComponentsLength);
flushBatchedUpdates();
} else {
dirtyComponents.length = 0;
}
},
};

var UPDATE_QUEUEING = {
initialize: function() {
this.callbackQueue.reset();
},
close: function() {
this.callbackQueue.notifyAll();
},
};
事务名称作用
NESTED_UPDATES标记事务开始时候的dirtyComponents并在结束时候对其进行更新,并从中删除更新的dirtyComponents元素
UPDATE_QUEUEING