【React】React 18 并发特性

React 18 引入了 并发特性(Concurrent Features),这是一次对 React 渲染机制的重大升级,让 React 更加智能、响应更流畅、资源更节省。

我们来详细讲解一下它的原理、特性、API 以及实际应用。


🧠 一、什么是并发特性(Concurrent Features)?

传统的 React 是 同步阻塞渲染:一旦开始渲染某个更新,就必须做完,不能中断,哪怕是非常耗时的任务,因此可能会发生阻塞。

之前,有时候加载一个复杂的图表,或者切换到一个内容很多的页面,整个页面就突然卡住,这就是 UI 卡顿或者说主线程阻塞。

React 18 引入的"并发特性"允许 React:

  • 中断当前的渲染任务,优先处理更紧急的更新(比如用户输入、动画)。
  • 将更新分片执行,避免阻塞主线程。
  • 自动调度优先级高的任务先执行,提高响应速度。
  • 支持 延迟加载(lazy loading)和流式渲染(Streaming)

这种机制称为 Concurrent Rendering(并发渲染),实现更智能的调度。

⚠️ 并不是你写的代码是"并发的",而是 React 的内部调度机制变得更智能了。

⚠️ 并发不是并行,js 还是单线程,并发是任务调度策略。


🚀 二、如何启用并发特性?

从 React 18 开始,默认就支持并发功能,但 必须使用新的 createRoot API 才能激活。

jsx 复制代码
// React 17 旧写法(不会启用并发特性)
ReactDOM.render(<App />, document.getElementById('root'));

// React 18 新写法(启用并发特性)
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

🔧 三、React 18 中的关键并发特性 API

1. startTransition()

用于把非紧急的更新(如筛选、排序、搜索)标记为 可中断任务

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

function App() {
  const [input, setInput] = useState('');
  const [list, setList] = useState([]);

  function handleChange(e) {
    const value = e.target.value;
    setInput(value);

    // 标记为"过渡更新" => 可中断
    startTransition(() => {
      const filtered = hugeDataList.filter(item =>
        item.includes(value)
      );
      setList(filtered);
    });
  }

  return (
    <>
      <input value={input} onChange={handleChange} />
      <ul>
        {list.map(item => <li key={item}>{item}</li>)}
      </ul>
    </>
  );
}

如果用户继续输入,React 会 中断上一个过滤渲染,执行最新的更新


2. useTransition()

useTransition 标记"不那么紧急"的更新。

允许将状态更新标记为"过渡(Transition)", 降低其优先级。返回isPending

(布尔值,指示过渡是否待处理)和startTransition(函数,用于包裹低优先级状态更新)。

核心:控制状态更新的"时机"或"优先级",避免阻塞高优先级交互。

更细粒度的过渡控制,可用于显示"加载中"状态:

jsx 复制代码
const [isPending, startTransition] = useTransition();

startTransition(() => {
  // 更新 state
});
jsx 复制代码
{isPending ? '加载中...' : '加载完成'}

3. Suspense(改进支持)

React 18 支持在客户端和服务端都使用 Suspense 进行延迟加载。

jsx 复制代码
import { Suspense, lazy } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

Suspense 是并发渲染支持的重要基础之一。


4. useDeferredValue()

用于延迟某个值的变化,防止其触发高开销渲染。

jsx 复制代码
const deferredInput = useDeferredValue(input);

可以搭配 input 实时搜索场景使用,提升体验。

useTransition 和 useDeferredValue 的区别

  1. 控制点不同
    • useTransition: 允许你包裹状态更新的逻辑(setState)。你明确指
      定哪个更新是低优先级的
    • useDeferredValue:允许你包裹一个值(通常是props或派生状态)。你关注的是值的延迟版本,而非更新过程。
  2. API 和反馈机制
    • useTransition返回isPending状态和startTransition函数。isPending可直接用于展示加载 UI。
    • useDeferredValue:仅返回一个延迟后的值。如需加载状态,通常需自行比较原始值和延迟值(e.g,text!=deferredText)
  3. 发起者视角
    • useTransition:主动行为
    • useDeferredValue:被动/响应式行为

🧩 四、实际应用场景举例

应用场景 传统做法的问题 React 18 的解决方案
用户输入时,触发大数据渲染 输入卡顿、掉帧 startTransition 异步更新结果
输入框绑定了复杂运算的数据 实时更新导致卡顿 useDeferredValue 缓更新
懒加载组件过慢 首屏空白或阻塞渲染 Suspense 延迟加载并展示 loading
SSR 流式渲染 一次性渲染,TTFB 高 React 18 支持 streaming(ReactDOMServer)

搜索/筛选大型列表:

外部数据源的图表/可视化:

图表接收频繁更新的 data prop,导致重绘耗时,引发卡顿。

编辑器草稿与实时预览

用户输入内容(高优先级),旁边的预览窗口根据输入实时渲染 Markdown 或复杂格式(低优先级)。

两者皆有可能,取决于状态组织方式:

  • useTransition:若编辑器和预览内容是分别的状态,用它包裹更
    新预览内容的状态。
  • useDeferredValue:若预览组件直接接收编辑器内容作 prop , 用
    它延迟预览组件的渲染

🎯 总结

特性 说明
startTransition 标记某些更新为可中断、可延迟
useTransition 用于显示"挂起状态"
useDeferredValue 推迟输入值更新,防止重复高开销渲染
Suspense 组件级懒加载或延迟展示
createRoot 启用并发渲染的前提

如果你希望我结合某个真实业务场景(比如表单输入、分页、搜索)给你写一个完整的 React18 并发特性 demo,也可以告诉我,我可以为你提供代码。

相关推荐
剪刀石头布啊14 分钟前
css外边距重叠问题
前端
剪刀石头布啊15 分钟前
chrome单页签内存分配上限问题,怎么解决
前端
剪刀石头布啊17 分钟前
css实现一个宽高固定百分比的布局的一个方式
前端
剪刀石头布啊20 分钟前
js数组之快速组、慢数组、密集数组、稀松数组
前端
mango_mangojuice42 分钟前
Linux学习笔记(make/Makefile)1.23
java·linux·前端·笔记·学习
Days20501 小时前
简单处理接口返回400条数据本地数据分页加载
前端
Novlan11 小时前
@tdesign/uniapp 图标瘦身
前端
ManThink Technology1 小时前
如何使用EBHelper 简化EdgeBus的代码编写?
java·前端·网络
. . . . .2 小时前
shadcn组件库
前端
2501_944711432 小时前
JS 对象遍历全解析
开发语言·前端·javascript