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 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax