React 并发特性实战指南
react 的并发特性让应用更加流畅和响应迅速。这些特性包括 useTransition、useDeferredValue、Suspense 和 useoptimistic。它们协同工作,能够显著提升用户体验。下面通过实际示例和最佳实践来了解这些功能。
理解并发渲染
并发渲染是 React 其他并发特性的基础。在并发渲染之前,React 采用同步更新方式。当触发更新时,React 会阻塞主线程,直到整个组件树重新渲染完成。这可能导致界面卡顿,特别是在处理复杂组件时,用户输入会变得迟钝。
并发渲染引入了可中断的渲染机制。React 可以开始渲染更新,然后暂停去处理更紧急的任务(比如用户输入),之后再从中断的地方继续渲染。这样就不会阻塞用户操作。
useTransition:管理任务优先级
useTransition 可以将状态更新标记为非紧急任务,让 React 能够中断这些更新去处理更重要的用户操作。
基本用法
const [isPending, startTransition] = useTransition();useTransition 不接收参数,返回一个数组。数组包含 isPending(表示是否有过渡任务在进行)和 startTransition(用于标记非紧急更新的函数)。
处理复杂渲染
当有复杂计算可能阻塞用户交互时,可以使用 useTransition 来降低优先级。
标签切换组件示例:
function TabButton({ children, onTabChange }) {
const [isPending, startTransition] = useTransition();
const handleClick = () => {
startTransition(() => {
onTabChange();
});
};
return (
<button
onClick={handleClick}
style={{
opacity: isPending ? 0.6 : 1,
transition: 'opacity 0.2s'
}}
>
{children}
</button>
);
}处理异步操作
对于 api 调用等异步操作,useTransition 可以协调状态更新,避免界面闪烁。
删除按钮示例:
function DeleteButton({ itemId, onDelete }) {
const [isPending, startTransition] = useTransition();
const handleDelete = async () => {
startTransition(async () => {
await onDelete(itemId);
});
};
return (
<button
onClick={handleDelete}
disabled={isPending}
style={{
opacity: isPending ? 0.6 : 1
}}
>
{isPending ? '删除中...' : '删除'}
</button>
);
}使用异步函数时,isPending 状态会自动管理,不需要手动设置加载状态。
Suspense:声明式加载
Suspense 提供了声明式的加载边界,可以与 React.lazy 配合实现代码分割,也可以用于异步数据加载。
基本用法
<Suspense fallback={<LoadingComponent />}>
<AsyncComponent />
</Suspense>Suspense 接收 fallback 属性(加载期间显示的组件)和可能异步加载的子组件。
代码分割
配合 React.lazy 实现组件级代码分割:
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<HeavyComponent />
</Suspense>
);
}异步数据加载
为不同数据源设置独立的 Suspense 边界:
function UserDashboard({ userId }) {
return (
<div>
<h1>用户面板</h1>
<Suspense fallback={<div>加载用户信息...</div>}>
<UserProfile userId={userId} />
</Suspense>
<Suspense fallback={<div>加载文章列表...</div>}>
<UserPosts userId={userId} />
</Suspense>
</div>
);
}每个组件管理自己的数据,父组件通过 Suspense 统一处理加载状态。
结合 useTransition
将 useTransition 与 Suspense 结合,可以在导航时避免突兀的加载状态。
路由组件示例:
function AppRouter() {
const [currentPage, setCurrentPage] = useState('home');
const [isPending, startTransition] = useTransition();
function navigate(page) {
startTransition(() => {
setCurrentPage(page);
});
}
return (
<div>
<nav>
<button onClick={() => navigate('home')}>首页</button>
<button onClick={() => navigate('profile')}>个人资料</button>
<button onClick={() => navigate('settings')}>设置</button>
</nav>
<Suspense fallback={<div>页面加载中...</div>}>
<div style={{
opacity: isPending ? 0.7 : 1,
transition: 'opacity 0.2s'
}}>
<PageContent page={currentPage} />
</div>
</Suspense>
</div>
);
}在 Next.js App Router 中,导航会自动使用 useTransition。需要注意的是,useTransition 只会等待足够长的时间来避免隐藏已显示的内容,不会等待所有嵌套的 Suspense 边界。
use:读取 Promise 和 Context
use API 可以读取 Promise 和 Context 值,与 Suspense 配合良好。与 Hooks 不同,use 可以在条件语句中调用。
基本用法
const data = use(promise);
const contextValue = use(Context);use 接收 Promise 或 Context,返回解析后的值。对于 Promise,会暂停组件直到解析完成。
完整示例
function App() {
const userPromise = fetchUser('/api/user/123');
return (
<Suspense fallback={<div>加载用户信息...</div>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}
function UserProfile({ userPromise }) {
const user = use(userPromise);
return (
<div>
<img src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
</div>
);
}use 会暂停 UserProfile 组件直到 Promise 解析完成,Suspense 在加载期间显示回退内容。
useDeferredValue:延迟更新
useDeferredValue 可以延迟更新依赖于频繁变化值的 UI 部分,在当前内容可见的同时准备新内容。
基本用法
const deferredValue = useDeferredValue(value);useDeferredValue 接收一个值,返回一个延迟版本,在快速更新期间会暂时保持旧值。
处理复杂渲染
当用户输入触发复杂计算时,使用 useDeferredValue 保持输入响应性:
function FilteredList({ items }) {
const [filter, setFilter] = useState('');
const deferredFilter = useDeferredValue(filter);
return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="搜索..."
/>
<ExpensiveFilteredItems
items={items}
filter={deferredFilter}
/>
</div>
);
}
const ExpensiveFilteredItems = React.memo(function ({ items, filter }) {
const filteredItems = items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
return (
<div>
{filteredItems.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
});使用 React.memo 包装复杂组件,确保只在 props 实际变化时重新渲染。
异步搜索
结合 useDeferredValue 和 Suspense 实现搜索功能:
function SearchApp() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="搜索用户..."
/>
<div style={{
opacity: isStale ? 0.6 : 1,
transition: 'opacity 0.2s'
}}>
<Suspense fallback={<div>搜索中...</div>}>
<SearchResults query={deferredQuery} />
</Suspense>
</div>
</div>
);
}
function SearchResults({ query }) {
if (!query) {
return <div>请输入搜索关键词</div>;
}
const results = use(searchUsers(query));
return (
<div>
{results.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}通过比较当前查询词和延迟版本,使用透明度变化提示用户搜索结果正在更新。
useOptimistic:乐观更新
useOptimistic 可以立即显示预期更新,而实际更新在后台进行。必须在 useTransition 中使用。
基本用法
const [optimisticState, addOptimistic] = useOptimistic(
currentState,
updateFunction
);接收当前状态和更新函数,返回乐观状态和触发乐观更新的函数。
实时交互
点赞按钮的乐观更新示例:
function LikeButton({ post, onToggleLike }) {
const [, startTransition] = useTransition();
const [optimisticPost, setOptimisticPost] = useOptimistic(
post,
(currentPost, shouldLike) => ({
...currentPost,
liked: shouldLike,
likes: currentPost.likes + (shouldLike ? 1 : -1)
})
);
const handleClick = () => {
startTransition(async () => {
setOptimisticPost(!optimisticPost.liked);
await onToggleLike(post.id, !optimisticPost.liked);
});
};
return (
<button onClick={handleClick}>
{optimisticPost.liked ? '是' : '否'} {optimisticPost.likes}
</button>
);
}点击时图标和计数立即变化,不需要额外的加载状态。
表单提交
评论表单的乐观更新:
function CommentForm({ comments, onSubmit }) {
const [optimisticComments, addOptimisticComment] = useOptimistic(
comments,
(currentComments, newComment) => [
...currentComments,
{ ...newComment, isSubmitting: true }
]
);
const formRef = useRef();
const handleSubmit = async (formData) => {
const commentText = formData.get('comment');
addOptimisticComment({
id: Date.now(),
text: commentText
});
await onSubmit(commentText);
formRef.current?.reset();
};
return (
<div>
<form ref={formRef} action={handleSubmit}>
<textarea name="comment" required />
<button type="submit">发布评论</button>
</form>
<div>
{optimisticComments.map(comment => (
<div
key={comment.id}
style={{
opacity: comment.isSubmitting ? 0.7 : 1
}}
>
{comment.text}
{comment.isSubmitting && ' (发布中...)'}
</div>
))}
</div>
</div>
);
}提交表单时评论立即显示,带有发布中状态。请求完成后,乐观评论会被真实数据替换。如果请求失败,useOptimistic 会自动回滚。
总结
React 的并发特性共同构建了更流畅的用户体验:
useTransition:创建低优先级的状态更新,在处理复杂任务和异步操作时保持界面响应
useDeferredValue:延迟渲染依赖频繁变化值的 UI 部分,防止界面卡顿
Suspense:为代码分割和异步操作提供声明式加载边界
useOptimistic:通过乐观更新让用户交互感觉更加即时
合理运用这些特性,可以显著提升 React 应用的性能和用户体验。在实际开发中,建议根据具体场景选择合适的并发特性,避免过度使用。
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!