React 18 新特性
问题
React 18 有哪些新特性?Concurrent Mode 是什么?useTransition 和 useDeferredValue 怎么用?
答案
React 18 的核心是 Concurrent Rendering(并发渲染),带来了自动批处理、Transitions、Suspense 增强等重要特性。
核心特性概览
| 特性 | 说明 |
|---|---|
| Concurrent Rendering | 可中断渲染,优先级调度 |
| Automatic Batching | 所有场景自动批量更新 |
| Transitions | 区分紧急和非紧急更新 |
| Suspense 增强 | 支持数据获取,SSR 流式渲染 |
| 新 Hooks | useTransition、useDeferredValue、useId 等 |
| 新 Root API | createRoot 替代 render |
新的 Root API
React 17
import ReactDOM from 'react-dom';
// 旧 API
ReactDOM.render(<App />, document.getElementById('root'));
// 卸载
ReactDOM.unmountComponentAtNode(document.getElementById('root'));
React 18
import { createRoot } from 'react-dom/client';
// 新 API
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
// 卸载
root.unmount();
为什么改变?
新 API 是启用 Concurrent 特性的前提。createRoot 启用了并发渲染模式。
Automatic Batching
React 17 的限制
// React 17
function handleClick() {
// ✅ 批量更新
setCount(c => c + 1);
setFlag(true);
}
setTimeout(() => {
// ❌ 不批量,两次渲染
setCount(c => c + 1);
setFlag(true);
}, 0);
fetch('/api').then(() => {
// ❌ 不批量,两次渲染
setCount(c => c + 1);
setFlag(true);
});
React 18 自动批处理
// React 18 - 所有场景都批量更新
function handleClick() {
// ✅ 批量
setCount(c => c + 1);
setFlag(true);
}
setTimeout(() => {
// ✅ 批量(新!)
setCount(c => c + 1);
setFlag(true);
}, 0);
fetch('/api').then(() => {
// ✅ 批量(新!)
setCount(c => c + 1);
setFlag(true);
});
document.addEventListener('click', () => {
// ✅ 批量(新!)
setCount(c => c + 1);
setFlag(true);
});
退出批处理
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// DOM 已更新
flushSync(() => {
setFlag(true);
});
// DOM 已更新
}
Concurrent Rendering
并发渲染原理
优先级调度
// React 18 Lanes 优先级
const SyncLane = 0b0000001; // 同步(最高)
const InputContinuousLane = 0b0000100; // 连续输入
const DefaultLane = 0b0010000; // 默认
const TransitionLane = 0b1000000; // 过渡(低)
useTransition
标记非紧急更新,保持 UI 响应:
import { useTransition, useState } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<string[]>([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
// 紧急更新:输入框立即响应
setQuery(value);
// 非紧急更新:可以被中断
startTransition(() => {
// 昂贵的计算
const filtered = filterLargeList(value);
setResults(filtered);
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<ul>
{results.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}
useTransition vs setTimeout
| 特性 | useTransition | setTimeout |
|---|---|---|
| 阻塞输入 | ❌ 不阻塞 | ✅ 不阻塞 |
| 立即开始 | ✅ 是 | ❌ 延迟 |
| 可中断 | ✅ 是 | ❌ 否 |
| 显示中间状态 | ✅ isPending | 需手动管理 |
// setTimeout:延迟执行
const handleChange = (value: string) => {
setQuery(value);
setTimeout(() => {
setResults(filterLargeList(value));
}, 0);
};
// useTransition:立即开始,可中断
const handleChange = (value: string) => {
setQuery(value);
startTransition(() => {
setResults(filterLargeList(value));
});
};
useDeferredValue
延迟更新某个值,类似防抖但更智能:
import { useDeferredValue, useMemo, useState } from 'react';
function SearchResults({ query }: { query: string }) {
// 延迟 query 更新
const deferredQuery = useDeferredValue(query);
// 只有 deferredQuery 变化时才重新计算
const results = useMemo(
() => filterLargeList(deferredQuery),
[deferredQuery]
);
// 判断是否在等待
const isStale = query !== deferredQuery;
return (
<div style={{ opacity: isStale ? 0.5 : 1 }}>
{results.map(item => (
<div key={item}>{item}</div>
))}
</div>
);
}
function SearchPage() {
const [query, setQuery] = useState('');
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
/>
<SearchResults query={query} />
</div>
);
}
useTransition vs useDeferredValue
| 特性 | useTransition | useDeferredValue |
|---|---|---|
| 控制对象 | 状态更新函数 | 值 |
| 适用场景 | 控制自己的 setState | 控制传入的 props |
| 返回值 | [isPending, startTransition] | deferredValue |
// useTransition:控制如何更新状态
const [isPending, startTransition] = useTransition();
startTransition(() => {
setData(newData);
});
// useDeferredValue:控制使用哪个值
const deferredData = useDeferredValue(data);
// 输入快速变化时,deferredData 更新会延迟
Suspense 增强
数据获取支持
// 配合支持 Suspense 的数据获取库
import { Suspense } from 'react';
import { useSuspenseQuery } from '@tanstack/react-query';
function UserProfile({ id }: { id: string }) {
// 数据加载时会抛出 Promise
const { data: user } = useSuspenseQuery({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
});
return <div>{user.name}</div>;
}
function App() {
return (
<Suspense fallback={<ProfileSkeleton />}>
<UserProfile id="123" />
</Suspense>
);
}
Suspense List(实验性)
import { SuspenseList, Suspense } from 'react';
function App() {
return (
<SuspenseList revealOrder="forwards" tail="collapsed">
<Suspense fallback={<Skeleton />}>
<ProfileDetails />
</Suspense>
<Suspense fallback={<Skeleton />}>
<ProfileTimeline />
</Suspense>
<Suspense fallback={<Skeleton />}>
<ProfileFriends />
</Suspense>
</SuspenseList>
);
}
| 属性 | 值 | 说明 |
|---|---|---|
| revealOrder | forwards | 从上到下依次显示 |
| revealOrder | backwards | 从下到上依次显示 |
| revealOrder | together | 全部就绪后一起显示 |
| tail | collapsed | 只显示一个 fallback |
| tail | hidden | 不显示 fallback |
新 Hooks
useId
生成稳定唯一的 ID,支持 SSR:
import { useId } from 'react';
function FormField({ label }: { label: string }) {
const id = useId();
return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} />
</div>
);
}
// 生成类似 :r1:, :r2: 的 ID
// 服务端和客户端一致
useSyncExternalStore
订阅外部存储,支持 Concurrent 模式:
import { useSyncExternalStore } from 'react';
function useOnlineStatus() {
const isOnline = useSyncExternalStore(
// subscribe
(callback) => {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
},
// getSnapshot
() => navigator.onLine,
// getServerSnapshot (SSR)
() => true
);
return isOnline;
}
function StatusBar() {
const isOnline = useOnlineStatus();
return <div>{isOnline ? '🟢 Online' : '🔴 Offline'}</div>;
}
useInsertionEffect
在 DOM 变更前同步执行,用于 CSS-in-JS 库:
import { useInsertionEffect } from 'react';
// 用于 CSS-in-JS 库
function useCSS(rule: string) {
useInsertionEffect(() => {
// 在 DOM 变更前注入样式
const style = document.createElement('style');
style.innerHTML = rule;
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}, [rule]);
}
Server Components
RSC 概念
// app/page.tsx - Server Component(默认)
async function Page() {
// 可以直接访问数据库、文件系统
const data = await db.query('SELECT * FROM posts');
return (
<div>
{data.map(post => (
<PostCard key={post.id} post={post} />
))}
<LikeButton /> {/* Client Component */}
</div>
);
}
// components/LikeButton.tsx - Client Component
'use client';
import { useState } from 'react';
export function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>
{liked ? '❤️' : '🤍'}
</button>
);
}
| 特性 | Server Component | Client Component |
|---|---|---|
| 运行环境 | 服务端 | 客户端 |
| 可用 Hooks | 无 | 所有 |
| 事件处理 | 无 | 有 |
| 访问后端 | 直接访问 | 需要 API |
| Bundle | 不打包 | 打包到客户端 |
Streaming SSR
传统 SSR
Streaming SSR
// 使用 Suspense 实现 Streaming
function Page() {
return (
<div>
<Header />
<Suspense fallback={<ArticleSkeleton />}>
<Article /> {/* 数据就绪后流式发送 */}
</Suspense>
<Suspense fallback={<CommentsSkeleton />}>
<Comments /> {/* 数据就绪后流式发送 */}
</Suspense>
</div>
);
}
常见面试问题
Q1: React 18 的 Concurrent Mode 是什么?
答案:
Concurrent Mode(并发模式)是 React 18 的核心特性,让 React 可以同时准备多个版本的 UI。
核心能力:
- 可中断渲染:高优先级更新可中断低优先级渲染
- 优先级调度:紧急更新(输入)优先于非紧急更新(列表)
- 时间切片:将长任务拆分,保持页面响应
// 启用并发模式
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
// 使用并发特性
const [isPending, startTransition] = useTransition();
startTransition(() => {
// 这个更新可被中断
setLargeList(filtered);
});
Q2: useTransition 和 useDeferredValue 有什么区别?
答案:
| 特性 | useTransition | useDeferredValue |
|---|---|---|
| 控制对象 | setState 函数 | 值 |
| 返回值 | [isPending, startTransition] | deferredValue |
| 使用场景 | 控制自己的状态更新 | 延迟使用传入的 props |
// useTransition:你控制状态
const [isPending, startTransition] = useTransition();
const handleChange = (value) => {
setInput(value);
startTransition(() => {
setFilteredList(filter(value)); // 低优先级
});
};
// useDeferredValue:你只接收值
function List({ searchQuery }) {
const deferredQuery = useDeferredValue(searchQuery);
// searchQuery 快速变化时,deferredQuery 延迟更新
}
Q3: React 18 的自动批处理有什么改进?
答案:
React 17 只在 React 事件处理器中批处理。React 18 所有场景都自动批处理:
// React 18 全场景批处理
setTimeout(() => {
setCount(1);
setFlag(true);
// 一次渲染(React 17 是两次)
}, 0);
fetch('/api').then(() => {
setCount(1);
setFlag(true);
// 一次渲染
});
退出批处理:使用 flushSync。
Q4: Suspense 在 React 18 有什么增强?
答案:
| 版本 | Suspense 能力 |
|---|---|
| React 16 | 只支持 React.lazy |
| React 18 | 数据获取、SSR Streaming |
// 数据获取
<Suspense fallback={<Loading />}>
<UserData /> {/* 使用支持 Suspense 的数据获取库 */}
</Suspense>
// SSR Streaming
// Suspense 边界内的内容可以流式发送
Q5: 什么是 Server Components?有什么好处?
答案:
Server Components 是只在服务端运行的 React 组件。
好处:
- 零客户端包体积:代码不发送到客户端
- 直接访问后端:直接查数据库、读文件
- 自动代码分割:Client Components 按需加载
// Server Component(默认)
async function Page() {
const data = await db.query('...'); // 直接访问数据库
return <div>{data.title}</div>;
}
// Client Component
'use client';
function LikeButton() {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>Like</button>;
}
限制:Server Components 不能使用 Hooks 和事件处理。