对react有一定了解的开发人员应该都听说过react的事务机制,这个机制几乎贯穿于react所有提供的方法,包括react中最常使用的setState函数
那么,react的事务机制到底是一种什么样的机制呢,为了解释事务机制的实现原理,react源码中用注释画出了这样一幅图
根据这幅图,我们可以这样理解事务,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
那么事务在react中到底是怎么应用的呢?我们透过下面这段代码来看看如何使用事务
var ReactUpdates = require('ReactUpdates');
var Transaction = require('Transaction');
var emptyFunction = require('emptyFunction');
//第二个wrapper
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
//第一个wrapper
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
//wrapper列表
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
//事务构造函数
function ReactDefaultBatchingStrategyTransaction() {
//原型中定义的初始化方法
this.reinitializeTransaction();
}
//继承原型
Object.assign(
ReactDefaultBatchingStrategyTransaction.prototype,
Transaction.Mixin,
{
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
}
);
//新建一个事务
var transaction = new ReactDefaultBatchingStrategyTransaction();
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
/**
* Call the provided function in a context within which calls to `setState`
* and friends are batched such that components aren't updated unnecessarily.
*/
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
callback(a, b, c, d, e);
} else {
//在这个地方调用事务,callback是从外部传入的方法,此处无需关心callback,只需要知道它是一个待执行方法即可
transaction.perform(callback, null, a, b, c, d, e);
}
},
};
代码不长,但确是react中用来控制渲染逻辑最关键的代码,此处只是为了展示事务的使用方法,我们无需关心代码做了些什么,我们可以看到上面的代码定义了一个叫ReactDefaultBatchingStrategyTransaction的构造函数,这个其实就是我们自定义的一个事务,并且在构造函数的原型上实现了一个叫getTransactionWrappers的接口,代码中的这个接口返回了一个数组,而这个数组的每一个项都包含了initalize和close方法,当调用perform方法时,事务会依次去调用这两个方法,而我们的看到的perform方法和构造函数中的reinitializeTransaction方法都是在Transaction.Mixin中定义的,这里面提供了实现事务所需要的所有基础功能,我们可以来看一下源代码的实现逻辑
'use strict';
var invariant = require('invariant');
var Mixin = {
//事务初始化函数
reinitializeTransaction: function() {
this.transactionWrappers = this.getTransactionWrappers();
if (this.wrapperInitData) {
this.wrapperInitData.length = 0;
} else {
this.wrapperInitData = [];
}
this._isInTransaction = false;
},
_isInTransaction: false,
//抽象接口,此处的getTransactionWrappers方法将会被外部定义的getTransactionWrappers方法所覆盖
getTransactionWrappers: null,
isInTransaction: function() {
return !!this._isInTransaction;
},
perform: function(method, scope, a, b, c, d, e, f) {
var errorThrown;
var ret;
try {
this._isInTransaction = true;
errorThrown = true;
//执行所有的initalize方法
this.initializeAll(0);
//执行真正的方法
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
if (errorThrown) {
try {
this.closeAll(0);
} catch (err) {
}
} else {
//正常状态下执行所有close方法
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;
},
//执行所有wrapper中的initialize函数
initializeAll: function(startIndex) {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
try {
//定义一个初始值,当finally里面匹配到this.wrapperInitData[i]未改变时,会忽略此项,从下一个项开始重新执行一次初始化,这么做的原因。。。官方解释为用catch使调试变的复杂,开心就好
this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
//执行initialize方法
this.wrapperInitData[i] = wrapper.initialize ?
wrapper.initialize.call(this) :
null;
} finally {
if (this.wrapperInitData[i] === Transaction.OBSERVED_ERROR) {
try {
this.initializeAll(i + 1);
} catch (err) {
}
}
}
}
},
//执行所有wrapper中的close函数
closeAll: function(startIndex) {
invariant(
this.isInTransaction(),
'Transaction.closeAll(): Cannot close transaction when none are open.'
);
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
var errorThrown;
try {
//标记是否有执行异常,这么做的原因。。。官方解释为用catch使调试变的复杂,开心就好
errorThrown = true;
//执行close方法
if (initData !== Transaction.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;
},
};
var Transaction = {
Mixin: Mixin,
OBSERVED_ERROR: {},
};
module.exports = Transaction;
这就是整个事务机制的实现代码,没有删减,逻辑非常清晰,也很简单,这里其实就是使用了原型模式,这个就是所有事务的原型,前面我们看到在自定义事务里面使用的perform方法和reinitializeTransaction方法都可以在这里面找到,其实核心原理就是定义一个原型,里面有一个用来存放wrappers(称之为套套吧)的抽象接口,会由继承于这个原型的子类覆盖,由子类来定义真正要执行的方法前后执行的所有东西,我们可以看到perform方法的逻辑其实也很清晰,就是先调用initializeAll方法执行所有的initialize方法,然后执行传入的主体方法,之后执行closeAll来执行所有的close方法,所以,看起来,目测也不是什么高深的东西,也就是在执行方法之前强行添加入口跟出口方法,代码比较简单,都加了注释,感兴趣的话可以自行了解一下
这里面有个比较骚的操作在于,不管在initializeAll还是closeAll中,都是使用一个临时变量来标记当前的执行过程是否有异常,舍弃了用catch方法,改用在finally方法里面处理异常,官方的说法是捕获错误会使调试变的困难,具体原因应该是因为作为基础的原型类,此处不应该去捕获外部传入方法抛出的异常,应该让异常直接抛回外部,由外部函数自行处理,如果此处强行将异常捕获,确实会导致外部函数调试变的比较困难