前言
React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读其源码是一个非常艰辛的过程。在学习 React 源码的过程中,给我帮助最大的就是这个系列文章,于是决定基于这个系列文章谈一下自己的理解。本文会大量用到原文中的例子,想体会原汁原味的感觉,推荐阅读原文。
本系列文章基于 React 15.4.2 ,以下是本系列其它文章的传送门:
React 源码深度解读(一):首次 DOM 元素渲染 - Part 1
React 源码深度解读(二):首次 DOM 元素渲染 - Part 2
React 源码深度解读(三):首次 DOM 元素渲染 - Part 3
React 源码深度解读(四):首次自定义组件渲染 - Part 1
React 源码深度解读(五):首次自定义组件渲染 - Part 2
React 源码深度解读(六):依赖注入
React 源码深度解读(七):事务 - Part 1
React 源码深度解读(八):事务 - Part 2
React 源码深度解读(九):单个元素更新
React 源码深度解读(十):Diff 算法详解
正文
在阅读 React 源码过程中,transaction 可以说无处不在,所有涉及到 UI 更新相关的操作都会借助 transaction 来完成。下面,我们就来看看它所起到的特殊所用。
Transaction 核心实现
Transaction 本质来说只是一个对象,它的核心方法是 perform:
perform: function <
A,
B,
C,
D,
E,
F,
G,
T: (a: A, b: B, c: C, d: D, e: E, f: F) => G // eslint-disable-line space-before-function-paren
>
(
method: T, scope: any,
a: A, b: B, c: C, d: D, e: E, f: F,
): G {
var errorThrown;
var ret;
try {
this._isInTransaction = true;
// Catching errors makes debugging more difficult, so we start with
// errorThrown set to true before setting it to false after calling
// close -- if it's still set to true in the finally block, it means
// one of these calls threw.
errorThrown = true;
this.initializeAll(0);
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
if (errorThrown) {
// If `method` throws, prefer to show that stack trace over any thrown
// by invoking `closeAll`.
try {
this.closeAll(0);
} catch (err) {}
} else {
// Since `method` didn't throw, we don't want to silence the exception
// here.
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;
},
可以看到,这个方法只做了 3 件事情:
- 执行初始化方法 initializeAll
- 执行传入的 callback 方法
- 执行收尾方法 closeAll
这里的结构很有意思,有 try 竟然没有 catch,取而代之的是 finally。说明就算抛出了错误,finally 部分的代码也要继续执行,随后再将错误往上层代码抛。这样能保证无论在什么情况下,closeAll 都能得到执行。
下面来看一下结构极其相似的 initializeAll 和 closeAll 方法:
initializeAll: function (startIndex: number): void {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
try {
// Catching errors makes debugging more difficult, so we start with the
// OBSERVED_ERROR state before overwriting it with the real return value
// of initialize -- if it's still set to OBSERVED_ERROR in the finally
// block, it means wrapper.initialize threw.
this.wrapperInitData[i] = OBSERVED_ERROR;
this.wrapperInitData[i] = wrapper.initialize ?
wrapper.initialize.call(this) :
null;
} finally {
if (this.wrapperInitData[i] === OBSERVED_ERROR) {
// The initializer for wrapper i threw an error; initialize the
// remaining wrappers but silence any exceptions from them to ensure
// that the first error is the one to bubble up.
try {
this.initializeAll(i + 1);
} catch (err) {}
}
}
}
},
...
closeAll: function (startIndex: number): void {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
var errorThrown;
try {
// Catching errors makes debugging more difficult, so we start with
// errorThrown set to true before setting it to false after calling
// close -- if it's still set to true in the finally block, it means
// wrapper.close threw.
errorThrown = true;
if (initData !== OBSERVED_ERROR && wrapper.close) {
wrapper.close.call(this, initData);
}
errorThrown = false;
} finally {
if (errorThrown) {
// The closer for wrapper i threw an error; close the remaining
// wrappers but silence any exceptions from them to ensure that the
// first error is the one to bubble up.
try {
this.closeAll(i + 1);
} catch (e) {}
}
}
}
this.wrapperInitData.length = 0;
},
transactionWrappers 是一个数组,一个 transaction 可以有多个 wrapper,通过 reinitializeTransaction 来初始化。每个 wrapper 都需要定义 initialize 和 close 方法。initializeAll 和 closeAll 都能保证其中一个 wrapper 成员抛出错误的时候,余下的 wrapper 能继续执行。initialize 有一个返回值,传给对应的 close 方法。当 initialize 抛出错误的时候,由于没有 catch,exception 会一直往上抛,中断了ret = method.call(scope, a, b, c, d, e, f)
的执行去到 finally,接着执行 closeAll。
了解 transaction 的基本概念后,我们来看下它是怎么应用的。
ReactDefaultBatchingStrategyTransaction
我们以ReactDefaultBatchingStrategyTransaction
为例子来看看 transaction 是怎么用的:
// transaction 子类
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
// 覆盖 transaction 的 getTransactionWrappers 方法
Object.assign(
ReactDefaultBatchingStrategyTransaction.prototype,
Transaction,
{
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
}
);
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
首先,ReactDefaultBatchingStrategyTransaction
继承了 transaction,并覆盖了getTransactionWrappers
这个方法来定义自己的 wrapper。这 2 个 wrapper 很简单,initialize
都是空函数,close 的时候就重置下标志位,然后再调用另一个方法。
下面再看一下创建ReactDefaultBatchingStrategyTransaction
的对象ReactDefaultBatchingStrategy
。
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) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
},
};
第一步是创建一个 ReactDefaultBatchingStrategyTransaction 实例。当batchedUpdates
第一次被调用的时候,alreadyBatchingUpdates
为 false,会调用transaction.perform
,让后续的操作都处于 transaction 的上下文之中。后面再调用batchedUpdates
的时候,只是单纯的执行callback
。
而调用ReactDefaultBatchingStrategy
的是ReactUpdates
,它通过依赖注入的方法在运行的时候将ReactDefaultBatchingStrategy
注入进去。
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
当enqueueUpdate
第一次执行的时候,它会检测是否在 batchUpdate 的模式下(batchingStrategy.isBatchingUpdates
),如果不是则调用batchingStrategy.batchedUpdates
,如果是则执行dirtyComponents.push(component)
。
当我们使用setState
的时候,它会调用ReactUpdates
的enqueueSetState
,然后再调用enqueueUpdate
。如果在 React 的生命周期函数又或者使用 React 自带的合成事件时,会在setState
之前先将整个处理过程设置为 batchUpdate 的模式,所以当我们setState
的时候,实际上只会执行dirtyComponents.push(component)
,并不会马上更新 state,这就是为什么setState
看似异步更新的原因。实际上它还是同步的。
以 React 生命周期函数为例子,当 Component 被初始化的时候,会执行_renderNewRootComponent
:
_renderNewRootComponent: function (
nextElement,
container,
shouldReuseMarkup,
context
) {
...
// The initial render is synchronous but any updates that happen during
// rendering, in componentWillMount or componentDidMount, will be batched
// according to the current batching strategy.
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
componentInstance,
container,
shouldReuseMarkup,
context
);
...
},
在这里就预先将整个处理过程设置为 batchUpdate 的模式了,官方的注释也说明了这点。
总结
我们再通过一张图,来总结下 transaction 是怎么被调用的。