React Concurrent 特性
1. Concurrent Rendering
传统同步渲染
┌──────────────────────────────────────┐
│ 更新开始 ─────────────────> 渲染完成 │
│ (不可中断) │
└──────────────────────────────────────┘
用户交互被阻塞 😢并发渲染
┌──────┬──────┬──────┬──────┬──────────┐
│ 渲染 │ 让出 │ 渲染 │ 让出 │ 提交完成 │
└──────┴──────┴──────┴──────┴──────────┘
中间穿插用户交互处理 😊2. Suspense 原理
基本用法
jsx
<Suspense fallback={<Loading />}>
<LazyComponent />
</Suspense>工作原理
- 子组件 抛出 Promise (throw thenable)
- Suspense 捕获 Promise,渲染 fallback
- Promise resolve 后,重新渲染子组件
源码简化
javascript
function updateSuspenseComponent(current, workInProgress) {
try {
const nextChildren = renderChild(workInProgress);
return nextChildren;
} catch (thrownValue) {
if (typeof thrownValue.then === 'function') {
// 是 Promise,显示 fallback
thrownValue.then(retrySuspenseComponent);
return renderFallback(workInProgress);
}
throw thrownValue; // 真正的错误
}
}数据获取模式
- Render-as-you-fetch: 推荐,请求与渲染并行
- Fetch-on-render: 传统,渲染时才发请求 (瀑布流)
3. useTransition
场景
用户输入触发大量重新渲染 (如搜索),希望输入保持响应。
用法
jsx
function SearchResults() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
// 高优: 立即更新输入框
setQuery(e.target.value);
// 低优: 可中断的结果更新
startTransition(() => {
setDeferredQuery(e.target.value);
});
};
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<ResultList query={deferredQuery} />
</>
);
}原理
startTransition 内的更新标记为 TransitionLane,优先级低于用户输入。
4. useDeferredValue
场景
延迟更新一个值,让 UI 保持响应。
用法
jsx
function SearchPage({ query }) {
const deferredQuery = useDeferredValue(query);
return (
<>
<SearchBox value={query} />
{/* 使用 deferred 值,更新可被打断 */}
<Suspense fallback={<Skeleton />}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}与 useTransition 的区别
| useTransition | useDeferredValue |
|---|---|
| 降级 更新动作 | 降级 值本身 |
| 需包裹 setState | 包裹值 |
| 控制更新时机 | 控制读取时机 |
5. Automatic Batching (React 18)
之前 (React 17)
javascript
// 在 setTimeout 中,每次 setState 触发一次渲染
setTimeout(() => {
setCount(c => c + 1); // 渲染
setFlag(f => !f); // 渲染
}, 0);现在 (React 18)
javascript
// 所有场景都自动批处理
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// 只有一次渲染!
}, 0);退出批处理
javascript
import { flushSync } from 'react-dom';
flushSync(() => setCount(c => c + 1)); // 立即渲染
flushSync(() => setFlag(f => !f)); // 立即渲染