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 实现了自动批量更新