前言
React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读其源码是一个非常艰辛的过程。在学习 React 源码的过程中,给我帮助最大的就是,于是决定基于这个系列文章谈一下自己的理解。本文会大量用到原文中的例子,想体会原汁原味的感觉,推荐阅读原文。
本系列文章基于 React 15.4.2 ,以下是本系列其它文章的传送门:
正文
在阅读 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 是怎么被调用的。