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

相关推荐
一位搞嵌入式的 genius2 小时前
前端实战开发(二):React + Canvas 网络拓扑图开发:6 大核心问题与完整解决方案
前端·前端框架
da_vinci_x2 小时前
设计稿秒出“热力图”:AI预测式可用性测试工作流,上线前洞察用户行为
前端·人工智能·ui·设计模式·可用性测试·ux·设计师
前端OnTheRun2 小时前
React18学习笔记(五) 【总结】常用的React Hooks函数,常用React-Redux Hooks函数和React中的组件通信
react.js·react-redux·组件通信
訾博ZiBo2 小时前
UI架构的“定海神针”:掌握“视图无关状态提升”原则
前端
Keepreal4962 小时前
谈谈对XSS,CSRF,SQL注入,DoS和DDoS攻击的理解以及如何预防
前端·安全
sunbyte3 小时前
每日前端宝藏库 | tinykeys ✨
前端·javascript
Demoncode_y3 小时前
Vue3 + Three.js 实现 3D 汽车个性化定制及展示
前端·javascript·vue.js·3d·汽车·three.js
Dontla3 小时前
Turbopack介绍(由Vercel开发的基于Rust的高性能前端构建工具,用于挑战传统构建工具Webpack、vite地位)Next.js推荐构建工具
前端·rust·turbopack
两个西柚呀3 小时前
nodejs中http模块搭建web服务器
服务器·前端·http