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 应用在各种异步场景下都能获得更好的性能表现。

相关推荐
Mintopia19 分钟前
🌐 数据合规框架下的 WebAIGC 训练数据处理技术规范
前端·javascript·aigc
骥龙39 分钟前
2.6、Web漏洞挖掘实战(下):XSS、文件上传与逻辑漏洞深度解析
前端·xss
用户6600676685391 小时前
从 var 到 let/const:JavaScript 变量声明的进化之路
javascript
用户433845375691 小时前
Promise深度解析,以及简易版的手写实现
前端
十年_H1 小时前
Cesium自定义着色器-片元着色器数据来源
javascript·cesium
梦之云1 小时前
state 状态相关
前端
梦之云1 小时前
effect 副作用相关
前端
UIUV1 小时前
var、let 与 const:JavaScript 变量声明的演进与最佳实践
javascript
golang学习记1 小时前
从0死磕全栈之Next.js 生产环境优化最佳实践
前端
Mintopia1 小时前
🧠 Next.js 还是 Nuxt.js?——当 JavaScript 碰上命运的分叉路
前端·后端·全栈