Appearance
并发模式概述
第一部分:并发模式介绍
1.1 什么是并发模式
并发模式(Concurrent Mode)是React 18引入的新特性,允许React同时准备多个版本的UI,并根据优先级智能地调度更新,从而提供更流畅的用户体验。
核心特点:
- 可中断的渲染
- 优先级调度
- 时间切片
- 自动批处理
- Suspense集成
与传统模式的对比:
javascript
// 传统模式(React 17及之前)
ReactDOM.render(<App />, document.getElementById('root'));
// - 同步渲染,不可中断
// - 一次只能准备一个UI版本
// - 长任务会阻塞主线程
// 并发模式(React 18+)
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
// - 异步渲染,可中断
// - 可同时准备多个UI版本
// - 自动时间切片,保持响应1.2 为什么需要并发模式
传统模式的问题:
javascript
// 问题1:长任务阻塞
function HeavyList({ items }) {
// 渲染10000个复杂项目
return (
<ul>
{items.map(item => (
<ExpensiveItem key={item.id} data={item} />
))}
</ul>
);
}
// 问题:渲染期间
// - 用户输入无响应
// - 动画卡顿
// - 页面冻结
// 问题2:无法区分优先级
function App() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleInput = (e) => {
setQuery(e.target.value); // 紧急
setResults(search(e.target.value)); // 非紧急
// 两个更新同等优先级,都会阻塞
};
}
// 问题3:加载状态管理复杂
function DataComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch('/api/data')
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
// 手动管理loading/error状态很繁琐
}并发模式的解决方案:
javascript
// 解决方案1:可中断渲染
function HeavyList({ items }) {
return (
<ul>
{items.map(item => (
<ExpensiveItem key={item.id} data={item} />
))}
</ul>
);
}
// 并发模式自动切片,保持响应
// 解决方案2:优先级调度
function App() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleInput = (e) => {
setQuery(e.target.value); // 高优先级,立即执行
startTransition(() => {
setResults(search(e.target.value)); // 低优先级,可中断
});
};
}
// 解决方案3:Suspense简化加载
function DataComponent() {
const data = use(fetchData()); // 自动suspend
return <div>{data.content}</div>;
}
function App() {
return (
<Suspense fallback={<Loading />}>
<DataComponent />
</Suspense>
);
}1.3 并发模式的核心概念
javascript
// 1. 可中断渲染
function interruptibleRendering() {
// React可以:
// - 开始渲染更新
// - 暂停渲染
// - 处理高优先级任务
// - 恢复或丢弃之前的渲染
}
// 2. 时间切片
function timeSlicing() {
// 大任务被分成5ms的小块
while (hasWork && !shouldYield()) {
doWork();
}
if (hasWork) {
// 让出控制权,下一帧继续
scheduleCallback(continueWork);
}
}
// 3. 优先级调度
const priorities = {
Immediate: 1, // 同步,立即执行
UserBlocking: 2, // 用户交互
Normal: 3, // 普通更新
Low: 4, // 低优先级
Idle: 5 // 空闲时执行
};
// 4. 并发特性
function concurrentFeatures() {
// - useTransition: 标记非紧急更新
// - useDeferredValue: 延迟更新
// - Suspense: 声明式加载
// - startTransition: 过渡API
}第二部分:启用并发模式
2.1 创建并发根
javascript
// React 18之前
import ReactDOM from 'react-dom';
ReactDOM.render(
<App />,
document.getElementById('root')
);
// React 18并发模式
import { createRoot } from 'react-dom/client';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
// 高级配置
const root = createRoot(container, {
// 配置选项
onRecoverableError: (error) => {
console.error('Recoverable error:', error);
},
identifierPrefix: 'my-app'
});
root.render(<App />);
// 卸载
root.unmount();2.2 Hydration根
javascript
// SSR应用的并发模式
import { hydrateRoot } from 'react-dom/client';
const container = document.getElementById('root');
hydrateRoot(container, <App />);
// 带配置的hydration
hydrateRoot(
container,
<App />,
{
onRecoverableError: (error, errorInfo) => {
console.error('Hydration error:', error);
console.log('Component stack:', errorInfo.componentStack);
}
}
);
// 完整的SSR设置
// server.js
import { renderToPipeableStream } from 'react-dom/server';
app.get('/', (req, res) => {
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/client.js'],
onShellReady() {
res.setHeader('content-type', 'text/html');
pipe(res);
}
});
});
// client.js
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('root'), <App />);2.3 严格模式与并发
javascript
// StrictMode在并发模式下更严格
import { StrictMode } from 'react';
function App() {
return (
<StrictMode>
<YourApp />
</StrictMode>
);
}
// StrictMode会:
// 1. 双重调用函数组件
function Component() {
console.log('render'); // 开发环境会打印两次
return <div>Content</div>;
}
// 2. 双重调用useEffect
function Component() {
useEffect(() => {
console.log('effect'); // 调用两次
return () => {
console.log('cleanup'); // 调用两次
};
}, []);
}
// 3. 检测不安全的生命周期
class OldComponent extends React.Component {
componentWillMount() { // 警告:不安全
// ...
}
}
// 4. 检测意外的副作用
function Component() {
const ref = useRef(0);
ref.current++; // 警告:渲染中的副作用
return <div>{ref.current}</div>;
}2.4 迁移到并发模式
javascript
// 渐进式迁移策略
// 步骤1:更新到React 18
npm install react@18 react-dom@18
// 步骤2:更新根API
// Before
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, container);
// After
import { createRoot } from 'react-dom/client';
const root = createRoot(container);
root.render(<App />);
// 步骤3:启用StrictMode检查问题
root.render(
<StrictMode>
<App />
</StrictMode>
);
// 步骤4:逐步采用并发特性
function App() {
// 开始使用useTransition
const [isPending, startTransition] = useTransition();
// 开始使用useDeferredValue
const deferredValue = useDeferredValue(value);
// 开始使用Suspense
return (
<Suspense fallback={<Loading />}>
<AsyncComponent />
</Suspense>
);
}
// 步骤5:测试和优化
// - 使用Profiler监控性能
// - 检查并发相关问题
// - 优化组件渲染第三部分:并发特性详解
3.1 useTransition
javascript
// useTransition标记非紧急更新
import { useState, useTransition } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (value) => {
// 紧急:立即更新输入框
setQuery(value);
// 非紧急:可中断的搜索
startTransition(() => {
const searchResults = performSearch(value);
setResults(searchResults);
});
};
return (
<div>
<input
value={query}
onChange={(e) => handleSearch(e.target.value)}
placeholder="搜索..."
/>
{isPending ? (
<div>搜索中...</div>
) : (
<SearchResults results={results} />
)}
</div>
);
}
// 高级用法
function TabContainer() {
const [tab, setTab] = useState('home');
const [isPending, startTransition] = useTransition();
const selectTab = (nextTab) => {
startTransition(() => {
setTab(nextTab);
});
};
return (
<>
<Tabs selectedTab={tab} onSelect={selectTab} />
{isPending && <Spinner />}
<TabContent tab={tab} />
</>
);
}
// 配置超时
function Component() {
const [isPending, startTransition] = useTransition({
timeoutMs: 3000 // 3秒后强制提交
});
const handleAction = () => {
startTransition(() => {
// 如果3秒内未完成,强制显示新UI
updateState();
});
};
}3.2 useDeferredValue
javascript
// useDeferredValue延迟更新
import { useState, useDeferredValue, useMemo } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const results = useMemo(
() => performSearch(deferredQuery),
[deferredQuery]
);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<div className={query !== deferredQuery ? 'dimmed' : ''}>
<Results data={results} />
</div>
</div>
);
}
// 与防抖的对比
function DebounceVsDeferred() {
const [query, setQuery] = useState('');
// 防抖:固定延迟
const debouncedQuery = useDebounce(query, 300);
// useDeferredValue:智能延迟
const deferredQuery = useDeferredValue(query);
// useDeferredValue的优势:
// 1. 自动调整延迟时间
// 2. 高优先级更新可以打断
// 3. 与React调度器集成
}
// 复杂场景
function FilteredList({ items }) {
const [filter, setFilter] = useState('');
const deferredFilter = useDeferredValue(filter);
const filteredItems = useMemo(() => {
if (!deferredFilter) return items;
return items.filter(item =>
item.name.toLowerCase().includes(deferredFilter.toLowerCase())
);
}, [items, deferredFilter]);
const isStale = filter !== deferredFilter;
return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="过滤..."
/>
<div style={{ opacity: isStale ? 0.5 : 1 }}>
<List items={filteredItems} />
</div>
</div>
);
}3.3 Suspense数据获取
javascript
// Suspense配合并发模式
import { Suspense } from 'react';
function App() {
return (
<Suspense fallback={<Loading />}>
<UserProfile />
<Posts />
</Suspense>
);
}
// 使用use() Hook
function UserProfile() {
const user = use(fetchUser());
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}
// 嵌套Suspense
function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
</Suspense>
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
</Suspense>
);
}
// SuspenseList(实验性)
import { SuspenseList } from 'react';
function Feed() {
return (
<SuspenseList revealOrder="forwards">
<Suspense fallback={<Skeleton />}>
<Post id={1} />
</Suspense>
<Suspense fallback={<Skeleton />}>
<Post id={2} />
</Suspense>
<Suspense fallback={<Skeleton />}>
<Post id={3} />
</Suspense>
</SuspenseList>
);
}3.4 自动批处理
javascript
// React 18的自动批处理
function handleClick() {
// React 18会自动批处理,只触发一次重渲染
setCount(c => c + 1);
setFlag(f => !f);
setValue(v => v * 2);
}
// 在异步代码中也会批处理
async function handleAsyncClick() {
await fetchData();
// React 18会批处理
setData(newData);
setLoading(false);
setError(null);
// 只触发一次重渲染
}
// setTimeout中也会批处理
function handleTimeout() {
setTimeout(() => {
// React 18会批处理
setCount(1);
setFlag(true);
// 只触发一次重渲染
}, 1000);
}
// 退出批处理
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// DOM已更新
flushSync(() => {
setFlag(f => !f);
});
// DOM已更新
// 触发了两次重渲染
}
// React 17 vs React 18
// React 17
function handleClick() {
setCount(c => c + 1); // 重渲染
setFlag(f => !f); // 重渲染
// 总共2次重渲染
}
// React 18
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// 总共1次重渲染
}第四部分:并发模式最佳实践
4.1 何时使用并发特性
javascript
// 场景1:实时搜索/过滤
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (value) => {
setQuery(value);
startTransition(() => {
// 大量数据的搜索
setResults(heavySearch(value));
});
};
return (
<>
<SearchInput value={query} onChange={handleSearch} />
{isPending && <SearchingIndicator />}
<Results data={results} />
</>
);
}
// 场景2:Tab切换
function TabPanel() {
const [tab, setTab] = useState('overview');
const [isPending, startTransition] = useTransition();
const switchTab = (newTab) => {
startTransition(() => {
setTab(newTab);
// 预加载Tab数据
prefetchTabData(newTab);
});
};
return (
<>
<TabBar activeTab={tab} onSwitch={switchTab} />
{isPending && <LoadingBar />}
<TabContent tab={tab} />
</>
);
}
// 场景3:数据可视化
function Chart({ data }) {
const [filter, setFilter] = useState('all');
const deferredFilter = useDeferredValue(filter);
const chartData = useMemo(
() => processChartData(data, deferredFilter),
[data, deferredFilter]
);
return (
<>
<FilterControls value={filter} onChange={setFilter} />
<ComplexChart
data={chartData}
isStale={filter !== deferredFilter}
/>
</>
);
}
// 场景4:无限滚动
function InfiniteList() {
const [items, setItems] = useState([]);
const [isPending, startTransition] = useTransition();
const loadMore = () => {
startTransition(() => {
fetchNextPage().then(newItems => {
setItems(prev => [...prev, ...newItems]);
});
});
};
return (
<>
<List items={items} />
{isPending && <LoadingMore />}
<button onClick={loadMore}>Load More</button>
</>
);
}4.2 性能优化策略
javascript
// 1. 合理使用memo
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
// 只有data变化时才重渲染
return <div>{processData(data)}</div>;
});
function Parent() {
const [count, setCount] = useState(0);
const [data] = useState(heavyData);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
{/* data没变,不会重渲染 */}
<ExpensiveComponent data={data} />
</div>
);
}
// 2. 优化Transition边界
function OptimizedApp() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
// ✅ 只包裹耗时操作
const handleSearch = (value) => {
setQuery(value); // 立即执行
startTransition(() => {
setResults(search(value)); // 可中断
});
};
// ❌ 不要包裹所有更新
const badSearch = (value) => {
startTransition(() => {
setQuery(value); // 不应该在transition中
setResults(search(value));
});
};
}
// 3. 使用Suspense边界
function App() {
return (
// ✅ 合理的Suspense粒度
<div>
<Header />
<Suspense fallback={<MainSkeleton />}>
<MainContent />
</Suspense>
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
</div>
);
// ❌ 太粗的粒度
// <Suspense fallback={<PageSkeleton />}>
// <EntireApp />
// </Suspense>
}
// 4. 预加载策略
function PreloadStrategy() {
const [tab, setTab] = useState('home');
const handleTabHover = (nextTab) => {
// 鼠标悬停时预加载
startTransition(() => {
preloadTabData(nextTab);
});
};
const handleTabClick = (nextTab) => {
setTab(nextTab);
};
return (
<Tabs
activeTab={tab}
onTabHover={handleTabHover}
onTabClick={handleTabClick}
/>
);
}4.3 错误处理
javascript
// 并发模式下的错误边界
class ConcurrentErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// 并发渲染中的错误
console.error('Concurrent render error:', error);
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} />;
}
return this.props.children;
}
}
// 使用
function App() {
return (
<ConcurrentErrorBoundary>
<Suspense fallback={<Loading />}>
<AsyncComponent />
</Suspense>
</ConcurrentErrorBoundary>
);
}
// 处理Transition错误
function Component() {
const [isPending, startTransition] = useTransition();
const [error, setError] = useState(null);
const handleAction = () => {
setError(null);
startTransition(() => {
try {
performAction();
} catch (err) {
setError(err);
}
});
};
if (error) {
return <ErrorMessage error={error} />;
}
return (
<button onClick={handleAction} disabled={isPending}>
{isPending ? 'Loading...' : 'Action'}
</button>
);
}4.4 测试并发组件
javascript
// 测试Transition
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('handles transition updates', async () => {
render(<SearchComponent />);
const input = screen.getByRole('textbox');
// 输入查询
await userEvent.type(input, 'react');
// 等待transition完成
await waitFor(() => {
expect(screen.getByText(/搜索中/)).toBeInTheDocument();
});
await waitFor(() => {
expect(screen.getByText(/results/i)).toBeInTheDocument();
});
});
// 测试Suspense
test('shows loading state', async () => {
render(
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
);
// 应该显示loading
expect(screen.getByText('Loading...')).toBeInTheDocument();
// 等待数据加载
await waitFor(() => {
expect(screen.getByText('Content')).toBeInTheDocument();
});
});
// 测试并发渲染
import { act } from '@testing-library/react';
test('concurrent rendering', async () => {
const { rerender } = render(<App count={0} />);
// 使用act包裹并发更新
await act(async () => {
rerender(<App count={1} />);
rerender(<App count={2} />);
});
expect(screen.getByText('2')).toBeInTheDocument();
});第五部分:性能监控
5.1 Profiler API
javascript
// 监控并发渲染性能
import { Profiler } from 'react';
function App() {
const onRenderCallback = (
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) => {
console.log('Profiler:', {
id,
phase,
actualDuration,
baseDuration,
efficiency: (baseDuration / actualDuration * 100).toFixed(2) + '%'
});
};
return (
<Profiler id="App" onRender={onRenderCallback}>
<Profiler id="Search" onRender={onRenderCallback}>
<SearchComponent />
</Profiler>
<Profiler id="Results" onRender={onRenderCallback}>
<ResultsComponent />
</Profiler>
</Profiler>
);
}
// 检测Transition性能
function TransitionProfiler() {
const [isPending, startTransition] = useTransition();
const startTimeRef = useRef(null);
const handleAction = () => {
startTimeRef.current = performance.now();
startTransition(() => {
performAction();
const duration = performance.now() - startTimeRef.current;
console.log('Transition duration:', duration);
if (duration > 100) {
console.warn('Slow transition:', duration);
}
});
};
}5.2 DevTools使用
javascript
// React DevTools标记
import { unstable_trace as trace } from 'scheduler/tracing';
function Component() {
const handleAction = () => {
trace('User Action', performance.now(), () => {
// 这个操作会在DevTools中标记
performAction();
});
};
return <button onClick={handleAction}>Action</button>;
}
// Concurrent特性追踪
function DebugConcurrent() {
const [isPending, startTransition] = useTransition();
useEffect(() => {
if (isPending) {
console.log('Transition started');
} else {
console.log('Transition completed');
}
}, [isPending]);
}5.3 性能指标收集
javascript
// 收集并发模式性能数据
class ConcurrentMetrics {
constructor() {
this.metrics = {
transitions: [],
suspensions: [],
renders: []
};
}
recordTransition(id, duration) {
this.metrics.transitions.push({
id,
duration,
timestamp: Date.now()
});
}
recordSuspension(component, duration) {
this.metrics.suspensions.push({
component,
duration,
timestamp: Date.now()
});
}
recordRender(component, phase, duration) {
this.metrics.renders.push({
component,
phase,
duration,
timestamp: Date.now()
});
}
getReport() {
return {
avgTransitionTime: this.average(this.metrics.transitions.map(t => t.duration)),
avgSuspensionTime: this.average(this.metrics.suspensions.map(s => s.duration)),
avgRenderTime: this.average(this.metrics.renders.map(r => r.duration)),
totalTransitions: this.metrics.transitions.length,
totalSuspensions: this.metrics.suspensions.length
};
}
average(arr) {
return arr.length > 0
? arr.reduce((a, b) => a + b, 0) / arr.length
: 0;
}
}
const metrics = new ConcurrentMetrics();
// 使用
function MonitoredComponent() {
const [isPending, startTransition] = useTransition();
const startRef = useRef(null);
useEffect(() => {
if (isPending) {
startRef.current = performance.now();
} else if (startRef.current) {
const duration = performance.now() - startRef.current;
metrics.recordTransition('action', duration);
}
}, [isPending]);
}注意事项
1. 兼容性考虑
浏览器兼容性:
- 现代浏览器完全支持
- IE需要polyfill
- 移动浏览器性能差异大
React版本:
- 需要React 18+
- 某些特性仍在实验阶段2. 常见陷阱
javascript
// ❌ 不要在Transition中读取ref
function Bad() {
const ref = useRef();
const [isPending, startTransition] = useTransition();
const handleClick = () => {
startTransition(() => {
const value = ref.current.value; // 可能不准确
setState(value);
});
};
}
// ✅ 在Transition外读取
function Good() {
const ref = useRef();
const [isPending, startTransition] = useTransition();
const handleClick = () => {
const value = ref.current.value; // 正确
startTransition(() => {
setState(value);
});
};
}
// ❌ 不要滥用Transition
function Bad() {
const [count, setCount] = useState(0);
startTransition(() => {
setCount(count + 1); // 简单更新不需要transition
});
}
// ✅ 只用于耗时操作
function Good() {
const [data, setData] = useState([]);
startTransition(() => {
setData(processLargeDataset()); // 合适
});
}3. 性能建议
javascript
// 1. 避免过度使用并发特性
// 2. 合理设置Suspense边界
// 3. 监控并发性能
// 4. 测试不同设备
// 5. 渐进式采用常见问题
Q1: 并发模式会自动提升性能吗?
A: 不会。需要正确使用Transition、Suspense等特性才能看到性能提升。
Q2: 所有应用都应该用并发模式吗?
A: 不一定。简单应用可能看不到明显收益。复杂、交互频繁的应用受益最大。
Q3: 如何判断是否需要使用Transition?
A: 当更新耗时超过100ms且不是用户直接交互时,考虑使用Transition。
Q4: useDeferredValue和防抖的区别?
A: useDeferredValue智能延迟,与React调度集成;防抖固定延迟,独立于React。
Q5: Suspense只能用于数据获取吗?
A: 不是。任何异步操作都可以,如代码分割、图片加载等。
Q6: 并发模式会破坏现有代码吗?
A: 大部分不会。但StrictMode会更严格,可能暴露潜在问题。
Q7: 如何测试并发特性?
A: 使用React Testing Library,配合act()和waitFor()。
Q8: 并发模式的内存开销大吗?
A: 有一定开销(双缓冲),但优化后影响不大。
Q9: 可以选择性使用并发特性吗?
A: 可以。创建Root后,可以选择在哪些组件使用Transition等特性。
Q10: React 19对并发模式有什么改进?
A: 更稳定的API、更好的性能、更多的并发特性。
总结
核心要点
1. 并发模式基础
✅ 可中断渲染
✅ 优先级调度
✅ 时间切片
✅ 自动批处理
2. 核心特性
✅ useTransition
✅ useDeferredValue
✅ Suspense
✅ 自动批处理
3. 最佳实践
✅ 合理使用Transition
✅ 优化Suspense边界
✅ 性能监控
✅ 错误处理
4. 注意事项
✅ 渐进式采用
✅ 测试充分
✅ 监控性能
✅ 处理兼容性实践建议
1. 从简单场景开始
2. 监控性能指标
3. 逐步优化
4. 团队培训
5. 持续改进并发模式是React的重要进化,合理使用可以显著提升用户体验。