React 事务机制
React 15 中用于批量更新的事务机制原理
问题
什么是 React 事务机制?它是如何实现批量更新的?
解答
什么是事务
事务(Transaction)是 React 15 及之前版本中的一种设计模式,用于包装一段代码,在执行前后自动执行特定的操作。
事务的执行流程:
事务执行流程
|------------------------------------------------------------------------|
| |
| initialize perform (执行业务代码) close |
| | | | |
| v v v |
| +-----------+ +---------------+ +-----------+ |
| | wrapper1 | | | | wrapper1 | |
| | initialize| ---> | anyMethod | ------> | close | |
| +-----------+ | | +-----------+ |
| +-----------+ +---------------+ +-----------+ |
| | wrapper2 | | wrapper2 | |
| | initialize| | close | |
| +-----------+ +-----------+ |
| |
|------------------------------------------------------------------------|
事务的基本实现
// 事务的简化实现
const Transaction = {
// 存储所有 wrapper
wrappers: [],
// 执行事务
perform(method) {
// 1. 执行所有 wrapper 的 initialize 方法
this.wrappers.forEach(wrapper => {
wrapper.initialize?.();
});
// 2. 执行核心方法
method();
// 3. 执行所有 wrapper 的 close 方法
this.wrappers.forEach(wrapper => {
wrapper.close?.();
});
}
};
React 中的事务应用
React 使用事务来实现 setState 的批量更新:
// 简化的批量更新事务
const FLUSH_BATCHED_UPDATES = {
initialize: function() {
// 初始化时不做任何事
},
close: function() {
// 关闭时执行批量更新
flushBatchedUpdates();
}
};
const RESET_BATCHED_UPDATES = {
initialize: function() {
// 记录之前的批量更新状态
this.previousIsBatchingUpdates = isBatchingUpdates;
isBatchingUpdates = true;
},
close: function() {
// 恢复之前的状态
isBatchingUpdates = this.previousIsBatchingUpdates;
}
};
// 批量更新事务
const ReactDefaultBatchingStrategyTransaction = {
wrappers: [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]
};
setState 批量更新原理
// 全局标志:是否处于批量更新模式
let isBatchingUpdates = false;
// 更新队列
let dirtyComponents = [];
// 简化的 setState 实现
function setState(component, partialState) {
// 将更新加入队列
component._pendingStateQueue.push(partialState);
if (isBatchingUpdates) {
// 批量模式:只入队,不立即更新
dirtyComponents.push(component);
} else {
// 非批量模式:立即更新
updateComponent(component);
}
}
// 事件处理函数会被事务包装
function handleClick() {
// 此时 isBatchingUpdates = true
this.setState({ count: 1 }); // 入队
this.setState({ count: 2 }); // 入队
this.setState({ count: 3 }); // 入队
// 事务 close 时统一更新
}
为什么 setTimeout 中 setState 是同步的
class Example extends React.Component {
state = { count: 0 };
handleClick = () => {
// 事件处理函数被事务包装,isBatchingUpdates = true
this.setState({ count: 1 });
console.log(this.state.count); // 0(异步)
setTimeout(() => {
// setTimeout 回调执行时,事务已结束
// isBatchingUpdates = false
this.setState({ count: 2 });
console.log(this.state.count); // 2(同步)
}, 0);
};
}
手写完整的事务机制
// 完整的事务实现
class Transaction {
constructor(wrappers) {
this.wrappers = wrappers || [];
}
perform(method, context, ...args) {
// 初始化阶段
const initializes = [];
this.wrappers.forEach((wrapper, i) => {
try {
initializes[i] = wrapper.initialize?.call(context);
} catch (e) {
console.error('Initialize error:', e);
}
});
// 执行阶段
let result;
try {
result = method.apply(context, args);
} catch (e) {
console.error('Perform error:', e);
}
// 关闭阶段
this.wrappers.forEach((wrapper, i) => {
try {
wrapper.close?.call(context, initializes[i]);
} catch (e) {
console.error('Close error:', e);
}
});
return result;
}
}
// 使用示例
const loggingWrapper = {
initialize() {
console.log('开始执行');
return Date.now();
},
close(startTime) {
console.log(`执行耗时: ${Date.now() - startTime}ms`);
}
};
const transaction = new Transaction([loggingWrapper]);
transaction.perform(() => {
console.log('执行业务逻辑');
});
// 输出:
// 开始执行
// 执行业务逻辑
// 执行耗时: 1ms
React 16+ 的变化
React 16 引入 Fiber 架构后,事务机制被废弃,改用新的调度机制:
// React 18 中使用 automatic batching
function handleClick() {
// 自动批量更新,无论在哪里调用
setState({ a: 1 });
setState({ b: 2 });
setTimeout(() => {
// React 18 中这里也是批量更新
setState({ c: 3 });
setState({ d: 4 });
}, 0);
}
// 如果需要同步更新,使用 flushSync
import { flushSync } from 'react-dom';
flushSync(() => {
setState({ count: 1 });
});
// 此时 DOM 已更新
关键点
- 事务是 React 15 中实现批量更新的机制,包含 initialize、perform、close 三个阶段
- wrapper 是事务的基本单元,可以在执行前后插入自定义逻辑
- setState 在事务中会被收集到队列,事务结束时统一更新
- setTimeout/Promise 中的 setState 在 React 15 是同步的,因为脱离了事务上下文
- React 16+ 使用 Fiber 架构替代事务,React 18 实现了自动批量更新
目录