React 18 引入的并发渲染(Concurrent Rendering)是近年来 React 架构上最大的一次升级。它并非一个新的 API,而是一套让渲染过程变得「可中断、可恢复」的底层能力。在并发模式下,React 可以暂停当前的渲染任务,去处理更高优先级的更新,然后再回来继续未完成的工作,从而保证主线程始终能响应用户交互。
并发渲染解决了什么
在旧的同步渲染模式下,一旦开始渲染一棵大型组件树,整个过程无法中断。如果组件层级很深、计算量大,就容易造成主线程长时间被占用,导致输入、滚动等交互出现卡顿。并发渲染把渲染拆分成多个小单元(fiber),让 React 可以在单元之间让出主线程。
useTransition:标记非紧急更新
useTransition 允许把某个状态更新标记为「过渡」,使其以低优先级执行。典型场景是搜索框输入触发的大列表过滤。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import { useState, useTransition } from 'react';
export function SearchBox({ items }: { items: string[] }) { const [query, setQuery] = useState(''); const [result, setResult] = useState(items); const [isPending, startTransition] = useTransition();
function handleChange(e: React.ChangeEvent<HTMLInputElement>) { const value = e.target.value; setQuery(value); startTransition(() => { setResult(items.filter((it) => it.includes(value))); }); }
return ( <> <input value={query} onChange={handleChange} placeholder="搜索…" /> {isPending && <span>过滤中…</span>} <ul> {result.map((it) => ( <li key={it}>{it}</li> ))} </ul> </> ); }
|
这样即便列表有上万条数据,用户输入依然流畅,因为过滤任务不会阻塞输入框的更新。
useDeferredValue:延迟消费一个值
useDeferredValue 与 useTransition 解决的是同一类问题,但视角不同:它接收一个值,返回它的「延迟版本」。当源值快速变化时,延迟值会沿用旧值,直到有空闲时间才追上新值。
1 2 3 4 5 6 7
| import { useDeferredValue, useMemo } from 'react';
function Chart({ data }: { data: number[] }) { const deferred = useDeferredValue(data); const heavy = useMemo(() => deferred.map((n) => n * 2), [deferred]); return <pre>{JSON.stringify(heavy)}</pre>; }
|
并发渲染并不是「多线程」,它依旧运行在主线程上,只是通过让出与恢复的策略,让交互优先于计算。理解这一点,才能合理地把 useTransition 与 useDeferredValue 用在真正昂贵的地方,而不是到处套用。