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

相关推荐
灵感__idea10 小时前
Hello 算法:贪心的世界
前端·javascript·算法
GreenTea12 小时前
一文搞懂Harness Engineering与Meta-Harness
前端·人工智能·后端
killerbasd13 小时前
牧苏苏传 我不装了 4/7
前端·javascript·vue.js
吴声子夜歌14 小时前
ES6——二进制数组详解
前端·ecmascript·es6
码事漫谈14 小时前
手把手带你部署本地模型,让你Token自由(小白专属)
前端·后端
ZC跨境爬虫14 小时前
【爬虫实战对比】Requests vs Scrapy 笔趣阁小说爬虫,从单线程到高效并发的全方位升级
前端·爬虫·scrapy·html
爱上好庆祝14 小时前
svg图片
前端·css·学习·html·css3
橘子编程14 小时前
JavaScript与TypeScript终极指南
javascript·ubuntu·typescript
王夏奇14 小时前
python中的__all__ 具体用法
java·前端·python
叫我一声阿雷吧15 小时前
JS 入门通关手册(45):浏览器渲染原理与重绘重排(性能优化核心,面试必考
javascript·前端面试·前端性能优化·浏览器渲染·浏览器渲染原理,重排重绘·reflow·repaint