This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/REACT/4. 进阶篇/13-并发特性.md
T
2026-04-29 22:19:51 +08:00

178 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 函数和清理逻辑缺失
```
## 关联笔记