178 lines
4.9 KiB
Markdown
178 lines
4.9 KiB
Markdown
---
|
||
tags: [React, Concurrent, Suspense, Transitions, Frontend]
|
||
create time: 2026-04-29 22:12
|
||
---
|
||
|
||
# 并发特性
|
||
|
||
## 概述
|
||
|
||
React 18 引入了并发渲染(Concurrent Rendering)架构,将 UI 更新划分为可中断、可恢复、可优先级调度的任务。理解并发的核心概念——Suspend、Transition、时间切片——能帮助你写出更流畅的用户体验。
|
||
|
||
## React 渲染架构演进
|
||
|
||
```mermaid
|
||
graph LR
|
||
A["React 17 同步渲染"] -->|"全部一次性完成"| B["长时间阻塞主线程 ❌"]
|
||
|
||
C["React 18 并发渲染"] -->|"可打断/可恢复"| D["Fiber Scheduler"]
|
||
D --> E["高优先级任务:用户输入 ⏩"]
|
||
D --> F["低优先级任务:数据加载 🐢"]
|
||
F -->|"被高优先级打断"| G["暂停 → 之后恢复 ✅"]
|
||
|
||
style B fill:#F5A87D,color:#000
|
||
style G fill:#4FC08D,color:#fff
|
||
```
|
||
|
||
## Suspense —— 声明式等待
|
||
|
||
### 基本用法
|
||
|
||
```tsx
|
||
// LazyComponent 在首次挂载时自动 code-split
|
||
const SettingsPage = lazy(() => import("./pages/SettingsPage"));
|
||
|
||
<Suspense fallback={<PageSkeleton />}>
|
||
<SettingsPage />
|
||
</Suspense>
|
||
```
|
||
|
||
### Suspense + Data Fetching(实验性 API)
|
||
|
||
```tsx
|
||
import { use } from "react";
|
||
|
||
// 资源标记为 Suspense-compatible
|
||
const promise = fetchData("/api/profile");
|
||
|
||
function Profile() {
|
||
// use() 会 suspend 直到 promise resolve
|
||
const profile = use(promise);
|
||
|
||
return <div>{profile.name}</div>;
|
||
}
|
||
|
||
// 外层用 Suspense 包裹
|
||
<Suspense fallback={<ProfileLoading />}>
|
||
<Profile />
|
||
</Suspense>
|
||
```
|
||
|
||
## useTransition —— 标记低优先级更新
|
||
|
||
```tsx
|
||
function SearchPage() {
|
||
const [query, setQuery] = useState("");
|
||
const [results, setResults] = useState<Result[]>([]);
|
||
const [isPending, startTransition] = useTransition();
|
||
|
||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const value = e.target.value;
|
||
|
||
// 立即响应输入
|
||
setQuery(value);
|
||
|
||
// 低优先级的结果更新(可被输入打断)
|
||
startTransition(() => {
|
||
setResults(heavySearch(value));
|
||
});
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<input value={query} onChange={handleInputChange} />
|
||
{isPending && <Spinner />}
|
||
<Results list={results} />
|
||
</>
|
||
);
|
||
}
|
||
```
|
||
|
||
### Transition 与直接 setState 对比
|
||
|
||
```mermaid
|
||
timeline
|
||
title "输入 "hello" 的渲染行为"
|
||
|
||
直接 setState : 每次按键 → re-render\n(h/h/e/l/o 共 5 次)
|
||
useTransition : h,e,l,l → 跳过中间\no → 最终渲染一次
|
||
```
|
||
|
||
## useDeferredValue —— 延迟副本
|
||
|
||
```tsx
|
||
function TodoApp() {
|
||
const [filter, setFilter] = useState("");
|
||
const deferredFilter = useDeferredValue(filter); // 延迟 ~16ms
|
||
|
||
return (
|
||
<>
|
||
{/* 快速响应用户输入 */}
|
||
<input value={filter} onChange={e => setFilter(e.target.value)} placeholder="过滤..." />
|
||
|
||
{/* 耗时操作使用延迟值 */}
|
||
<TodoList todos={allTodos} filter={deferredFilter} />
|
||
</>
|
||
);
|
||
}
|
||
```
|
||
|
||
> [!tip] Transition vs DeferredValue 选择指南
|
||
>
|
||
> | 场景 | 推荐 |
|
||
> |------|------|
|
||
> | 表单提交后展示新视图 | `useTransition` |
|
||
> | 输入框即时搜索 + 延迟加载 | `useDeferredValue` |
|
||
> | 多个状态联动更新 | `useTransition`(更明确控制粒度) |
|
||
> | 简单延迟一个值 | `useDeferredValue`(一行搞定) |
|
||
|
||
## 时间切片原理
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Browser as 浏览器主线程
|
||
participant Fiber as Fiber Scheduler
|
||
|
||
Browser->>Fiber: 构建整棵组件树(工作单元)
|
||
Fiber->>Browser: 渲染帧 1(约 16ms)✅
|
||
Note over Fiber,Browser: 把任务切分为 ~5ms 的工作单元
|
||
Browser->>Fiber: 下一帧开始
|
||
Fiber->>Browser: 渲染帧 2 ✅
|
||
Note over Fiber: 如果用户点击了按钮<br/>高优先级任务插入队列
|
||
Fiber-->>Browser: 暂停当前帧
|
||
Browser->>Fiber: 处理用户输入 ✅
|
||
Fiber->>Browser: 继续未完成的渲染帧
|
||
|
||
style Browser fill:#F5A87D,color:#000
|
||
style Fiber fill:#4FC08D,color:#fff
|
||
```
|
||
|
||
### 关键概念
|
||
|
||
- **Work Breakdown**:React 将渲染工作拆成多个小单元(work unit),每个单元约 1ms
|
||
- **Yield to Browser**:每个单元完成后让出控制权给浏览器,确保 UI 响应性
|
||
- **Priority Scheduling**:用户输入 > 网络响应 > 后台数据更新 > 动画
|
||
|
||
## 并发模式下的陷阱
|
||
|
||
> [!warning] 需要注意的行为变化
|
||
|
||
```tsx
|
||
// 1. useEffect 的执行时机变了
|
||
useEffect(() => {
|
||
console.log("effect");
|
||
}, [dep]);
|
||
// 效果不再与渲染同步,可能在异步 commit 阶段执行
|
||
|
||
// 2. 浏览器生命周期方法(如 getSnapshotBeforeUpdate)已过时
|
||
// 并发模式下无法保证同步 commit
|
||
|
||
// 3. 旧版第三方库可能与并发不兼容
|
||
// 遇到 "Cannot update a component during rendering" → 检查是否有不规范的 side effect
|
||
|
||
// 4. StrictMode 双重渲染(开发环境)
|
||
// 用于发现不纯的 render 函数和清理逻辑缺失
|
||
```
|
||
|
||
## 关联笔记
|