-
React 是利用更新队列
this._pendingStateQueue以及更新状态this._pendingReplaceState和this._pendingForceUpdate来实现 setState 的异步更新机制的。 -
在声明周期中,直接传递对象的 setState 会被合并成一次;使用函数传递 state 不会被合并。
// this.state.index 初始值为 1 // 使用对象传递 state componentDidMount() { this.setState({ index: this.state.index + 1 }, () => { console.log(this.state.index); // 2 }) this.setState({ index: this.state.index + 1 }, () => { console.log(this.state.index); // 2 }) setTimeout(()=> { // setTimeout 使用对象或函数传递 state 结果一样 this.setState((prevState) => ({ index: prevState.index + 1 }), () => { console.log(this.state.index); // 3 }) this.setState((prevState) => ({ index: prevState.index + 1 }), () => { console.log(this.state.index); // 4 }) }, 0) } // 使用函数传递 state componentDidMount() { this.setState((prevState) => ({ index: prevState.index + 1 }), () => { console.log(this.state.index); // 3 }) this.setState((prevState) => ({ index: prevState.index + 1 }), () => { console.log(this.state.index); // 3 }) setTimeout(()=> { this.setState((prevState) => ({ index: prevState.index + 1 }), () => { console.log(this.state.index); // 4 }) this.setState((prevState) => ({ index: prevState.index + 1 }), () => { console.log(this.state.index); // 5 }) }, 0) } -
setState 通过一个队列机制实现 state 更新。当执行 setState 时,会将需要更新的 state 合并后放入状态队列,而不会立即更新 this.state,队列机制可以高效地批量更新 state。
-
调用 setState 方法时实际执行了什么操作?
- 实际上会执行 enqueueSetState 方法,并对 partialState 以及 _pendingStateQueue 更新队列进行合并操作,最终通过 enqueueUpdate 方法执行 state 更新。
- 而 performUpdateIfNecessary 方法会获取 _pendingElement、_pendingStateQueue、_pendingForceUpdate,并调用 receiveComponent 和 updateComponent 方法进行组件更新。
-
enqueueUpdate 是如何更新 state 的?
先根据 batchingStrategy.isBatchingUpdates 的值判断当前是否处于批量更新模式:
- isBatchingUpdates 为 false,表示当前不处于批量更新模式,对所有队列中的更新执行 batchingStrategy.batchedUpdates 方法:遍历 dirtyComponents,调用 updateComponent 方法,更新 pending state 或 props。
- isBatchingUpdates 为 false, 表示当前处于批量更新模式,只将组件保存到 dirtyComponents 中。
function enqueueUpdate(component) { ensureInjected(); // 如果不处于批量更新模式 if (!batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component); return; } // 如果处于批量更新模式,则将该组件保存在 dirtyComponents 中 dirtyComponents.push(component); }batchingStrategy 是一个对象,定义了一个 isBatchingUpdates 的布尔值,和 batchedUpdates方法。
var ReactDefaultBatchingStrategy = { isBatchingUpdates: false, batchedUpdates: function(callback, a, b, c, d, e) { var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; ReactDefaultBatchingStrategy.isBatchingUpdates = true; if (alreadyBatchingUpdates) { /** 这一部分应该是因为在React 15.0之前的版本中,为开发者提供了batchedUpdates方法,所以可能会在批量更新模式中走入这部分逻辑。 后来移除了这个API后,应该不会在批量更新模式下走入这部分逻辑了吧。(待确认) **/ callback(a, b, c, d, e); } else { transaction.perform(callback, null, a, b, c, d, e); } } } -
什么是事务?
在batchedUpdates 方法中,调用了 transaction.perform,这个 transaction 即是事务。
事务是将需要执行的方法使用 wrapper 封装起来,再通过事务提供的 perform 方法执行。而在 perform 之前,先执行所有 wrapper 中的 initialize 方法,执行完 perform 后,再执行所有的 close 方法。一组 initialize 及 close 方法称为一个 wrapper。事务支持多个 wrapper 叠加。
在close 方法中,会将 isBatchingUpdates 的值置为 false。
-
如何实现自己需要的事务?
把事务提供的 mixin 方法混入自己的事务中,另外还要实现一个抽象的 getTransactionWrappers 接口。这个接口用来获取所有需要封装的前置方法(initialize)和收尾方法(close),因此它需要返回一个由对象组成的数组,每个对象分别有 key 为 initialize 和 close 的方法。
var Transaction = require('./Transaction'); var MyTransaction = function() { // ... }; Object.assign(MyTransaction.prototype, Transaction.Mixin, { getTransactionWrappers: function() { return [{ initialize: function() { console.log('before method transform'); }, close: function() { console.log('after method transform') } }]; } }); var transaction = new MyTransaction(); var testMethod = function() { console.log('test'); } transaction.perform(testMethod); // 执行结果 // before method transform // test // after method transform -
在 React 的生命周期和合成事件中,React 处于批量更新机制中,这时 isBatchingUpdate 为 true。这时无论调用多少次 setState, 都不会执行更新,而是将要更新的 state 存入 _pendingStateQueue,将要更新的组件存入 dirtyComponent。
-
当上一次更新机制执行完毕,isBatchingUpdate 会被置为 false,这时将执行之前累积的setState。
参考:
- 深入 React 技术栈,陈屹。
- 【掘金】【React深入】setState的执行机制: juejin.cn/post/684490…