React 18 的自动批处理

什么是批处理?

批处理是指 React 将多个状态更新合并为单个重新渲染的过程,以提高性能。

React 17 及之前的批处理

TypeScript 复制代码
// React 17 中,只有在 React 事件处理函数中才会批处理
function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    // ✅ React 17:在事件处理函数中会被批处理
    setCount(c => c + 1);
    setFlag(f => !f);
    // 只会触发一次重新渲染
  }

  useEffect(() => {
    // ❌ React 17:在 Promise、setTimeout 等中不会批处理
    fetchData().then(() => {
      setCount(c => c + 1); // 第一次渲染
      setFlag(f => !f);     // 第二次渲染
    });
  }, []);

  return <button onClick={handleClick}>点击</button>;
}

React 18 的自动批处理

React 18 在任何地方都默认启用自动批处理:

TypeScript 复制代码
import { useState, useEffect } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  // 1. 事件处理函数 - 仍然批处理
  function handleClick() {
    setCount(c => c + 1);
    setFlag(f => !f);
    // ✅ 只触发一次重新渲染
  }

  // 2. setTimeout - 现在也会批处理
  function handleTimeout() {
    setTimeout(() => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // ✅ React 18:只触发一次重新渲染
    }, 1000);
  }

  // 3. Promise - 现在也会批处理
  function handlePromise() {
    fetch('/api').then(() => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // ✅ React 18:只触发一次重新渲染
    });
  }

  // 4. 原生事件处理 - 现在也会批处理
  useEffect(() => {
    document.getElementById('external-btn').addEventListener('click', () => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // ✅ React 18:只触发一次重新渲染
    });
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Flag: {flag.toString()}</p>
      <button onClick={handleClick}>事件处理</button>
      <button onClick={handleTimeout}>setTimeout</button>
      <button onClick={handlePromise}>Promise</button>
      <button id="external-btn">原生事件</button>
    </div>
  );
}

如何退出批处理?

在某些情况下,你可能需要立即应用状态更新。React 18 提供了 flushSync 来退出批处理:

TypeScript 复制代码
import { useState } from 'react';
import { flushSync } from 'react-dom';

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    // 立即执行这个更新,不进行批处理
    flushSync(() => {
      setCount(c => c + 1);
    });
    
    // 此时 DOM 已经更新
    console.log('Count updated:', count + 1);
    
    // 这个更新会单独处理
    setFlag(f => !f);
    
    // 总共会触发两次重新渲染
  }

  return (
    <div>
      <p>Count: {count}</p>
      <p>Flag: {flag.toString()}</p>
      <button onClick={handleClick}>立即更新</button>
    </div>
  );
}

实际应用场景

1、表单处理

TypeScript 复制代码
function ContactForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [isSubmitting, setIsSubmitting] = useState(false);

  async function handleSubmit(event) {
    event.preventDefault();
    
    setIsSubmitting(true);
    
    try {
      // 这些状态更新会被自动批处理
      const response = await fetch('/api/contact', {
        method: 'POST',
        body: JSON.stringify({ name, email })
      });
      
      if (response.ok) {
        setName('');
        setEmail('');
        setIsSubmitting(false);
        // ✅ React 18:这三个更新会被批处理为一次渲染
      }
    } catch (error) {
      setIsSubmitting(false);
      // 错误处理
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={name} 
        onChange={(e) => setName(e.target.value)} 
        placeholder="姓名"
      />
      <input 
        value={email} 
        onChange={(e) => setEmail(e.target.value)} 
        placeholder="邮箱"
      />
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? '提交中...' : '提交'}
      </button>
    </form>
  );
}

2、购物车操作

TypeScript 复制代码
function ShoppingCart() {
  const [items, setItems] = useState([]);
  const [total, setTotal] = useState(0);
  const [isLoading, setIsLoading] = useState(false);

  async function addItem(product) {
    setIsLoading(true);
    
    try {
      const response = await fetch('/api/cart/add', {
        method: 'POST',
        body: JSON.stringify(product)
      });
      
      const result = await response.json();
      
      // 这些相关的状态更新会被自动批处理
      setItems(prev => [...prev, product]);
      setTotal(prev => prev + product.price);
      setIsLoading(false);
      // ✅ React 18:一次重新渲染而不是三次
      
    } catch (error) {
      setIsLoading(false);
      console.error('添加商品失败:', error);
    }
  }

  return (
    <div>
      {/* 组件内容 */}
    </div>
  );
}

3、与并发特性的结合

TypeScript 复制代码
import { useState, startTransition } from 'react';

function SearchComponent() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isSearching, setIsSearching] = useState(false);

  function handleSearch(newQuery) {
    setQuery(newQuery); // 立即更新输入框
    
    if (newQuery.length === 0) {
      // 这些更新会被批处理
      setResults([]);
      setIsSearching(false);
      return;
    }
    
    setIsSearching(true);
    
    // 使用 startTransition 标记非紧急更新
    startTransition(() => {
      searchAPI(newQuery).then(searchResults => {
        // 这些更新在并发模式下会被智能批处理
        setResults(searchResults);
        setIsSearching(false);
      });
    });
  }

  return (
    <div>
      <input 
        value={query}
        onChange={(e) => handleSearch(e.target.value)}
        placeholder="搜索..."
      />
      {isSearching && <div>搜索中...</div>}
      <SearchResults results={results} />
    </div>
  );
}

性能对比

TypeScript 复制代码
// 性能测试组件
function PerformanceTest() {
  const [renderCount, setRenderCount] = useState(0);
  const [state1, setState1] = useState(0);
  const [state2, setState2] = useState(0);
  const [state3, setState3] = useState(0);

  // 记录渲染次数
  useEffect(() => {
    setRenderCount(prev => prev + 1);
  });

  function triggerUpdates() {
    // 触发多个状态更新
    setState1(prev => prev + 1);
    setState2(prev => prev + 1);
    setState3(prev => prev + 1);
    
    // React 17(非事件处理中):3次重新渲染
    // React 18:1次重新渲染
  }

  return (
    <div>
      <p>渲染次数: {renderCount}</p>
      <p>State1: {state1}</p>
      <p>State2: {state2}</p>
      <p>State3: {state3}</p>
      <button onClick={triggerUpdates}>
        触发多个更新
      </button>
    </div>
  );
}

总结

React 18 的自动批处理带来了以下好处:

  1. 更好的性能:减少不必要的重新渲染

  2. 更一致的行为:无论在什么情况下,状态更新都有相同的批处理行为

  3. 更简单的代码:不需要担心批处理的条件,React 会自动处理

  4. 更好的用户体验:更流畅的界面更新

注意事项

  • 自动批处理是默认开启的,不需要额外配置

  • 使用 flushSync 可以退出批处理,但应谨慎使用

  • 批处理不会影响逻辑正确性,只是优化了渲染性能

  • 与 React 18 的其他新特性(如并发渲染)完美配合

这个特性让 React 应用在各种异步场景下都能获得更好的性能表现。

相关推荐
狂炫冰美式3 分钟前
不谈技术,搞点文化 🧀 —— 从复活一句明代残诗破局产品迭代
前端·人工智能·后端
xw51 小时前
npm几个实用命令
前端·npm
!win !1 小时前
npm几个实用命令
前端·npm
代码狂想家1 小时前
使用openEuler从零构建用户管理系统Web应用平台
前端
dorisrv2 小时前
优雅的React表单状态管理
前端
蓝瑟3 小时前
告别重复造轮子!业务组件多场景复用实战指南
前端·javascript·设计模式
dorisrv3 小时前
高性能的懒加载与无限滚动实现
前端
韭菜炒大葱3 小时前
别等了!用 Vue 3 让 AI 边想边说,字字蹦到你脸上
前端·vue.js·aigc
StarkCoder3 小时前
求求你,别在 Swift 协程开头写 guard let self = self 了!
前端
清妍_3 小时前
一文详解 Taro / 小程序 IntersectionObserver 参数
前端