04-性能优化与最佳实践——13. 乐观更新 - useOptimistic

发布时间:2026/6/27 1:03:50
04-性能优化与最佳实践——13. 乐观更新 - useOptimistic 13. 乐观更新 - useOptimistic概述乐观更新是一种提升用户体验的技术在异步操作如网络请求完成之前就立即更新 UI让用户感觉操作响应更即时。如果请求失败则回滚到之前的状态。React 19 引入了useOptimisticHook 来简化这一模式。维度内容What在异步操作完成前立即更新 UI失败后回滚Why提升用户体验让界面响应更即时When点赞、评论、添加购物车、发送消息等即时反馈场景Where函数组件中配合表单或用户交互Who需要即时反馈的开发者Howconst [optimisticState, addOptimistic] useOptimistic(state, updateFn)1. 传统方式 vs 乐观更新1.1 传统方式等待服务器响应// ❌ 传统方式用户需要等待服务器响应 function LikeButton() { const [liked, setLiked] useState(false); const [loading, setLoading] useState(false); const handleLike async () { setLoading(true); try { await fetch(/api/like, { method: POST }); setLiked(true); // 等待服务器响应后才更新 UI } catch (error) { console.error(点赞失败); } finally { setLoading(false); } }; return ( button onClick{handleLike} disabled{loading} {loading ? 处理中... : (liked ? 已点赞 : 点赞)} /button ); }问题用户点击后需要等待网络请求完成才能看到 UI 变化体验不够流畅。1.2 乐观更新立即响应// ✅ 乐观更新立即更新 UI失败后回滚 function LikeButton() { const [liked, setLiked] useState(false); const handleLike async () { // 立即更新 UI乐观更新 setLiked(true); try { await fetch(/api/like, { method: POST }); // 成功保持状态 } catch (error) { // 失败回滚 setLiked(false); alert(点赞失败请重试); } }; return button onClick{handleLike}{liked ? 已点赞 : 点赞}/button; }2. React 19 的 useOptimistic Hook2.1 基本语法import { useOptimistic } from react; function Component() { const [optimisticState, addOptimistic] useOptimistic( state, // 当前状态 (currentState, optimisticValue) { // 更新函数返回新的乐观状态 return { ...currentState, ...optimisticValue }; } ); // optimisticState: 可能是真实状态或乐观状态 // addOptimistic: 触发乐观更新的函数 }2.2 基础示例点赞按钮import { useOptimistic, useState } from react; function LikeButton({ initialLiked false, likeCount 0 }) { const [liked, setLiked] useState(initialLiked); const [count, setCount] useState(likeCount); // 乐观状态 const [optimisticLiked, setOptimisticLiked] useOptimistic( liked, (current, newValue) newValue ); const [optimisticCount, setOptimisticCount] useOptimistic( count, (current, increment) current increment ); const handleLike async () { // 立即执行乐观更新 setOptimisticLiked(!optimisticLiked); setOptimisticCount(optimisticLiked ? -1 : 1); try { const response await fetch(/api/like, { method: POST, body: JSON.stringify({ liked: !optimisticLiked }), }); if (!response.ok) throw new Error(点赞失败); const data await response.json(); // 同步真实状态 setLiked(data.liked); setCount(data.count); } catch (error) { // 失败时 useOptimistic 会自动回滚 console.error(点赞失败:, error); } }; return ( button onClick{handleLike} {optimisticLiked ? ❤️ : } {optimisticCount} /button ); }3. 实际应用场景3.1 待办事项列表import { useOptimistic, useState, useTransition } from react; function TodoApp() { const [todos, setTodos] useState([ { id: 1, text: 学习 React 19, completed: false }, { id: 2, text: 掌握 useOptimistic, completed: false }, ]); const [optimisticTodos, addOptimisticTodo] useOptimistic( todos, (state, newTodo) [...state, newTodo] ); const [isPending, startTransition] useTransition(); const addTodo async (text) { const newTodo { id: Date.now(), text, completed: false, optimistic: true, }; // 立即添加到列表乐观更新 startTransition(() { addOptimisticTodo(newTodo); }); try { const response await fetch(/api/todos, { method: POST, body: JSON.stringify({ text }), }); const savedTodo await response.json(); // 用服务器返回的真实数据替换乐观数据 setTodos(prev prev.map(todo todo.id newTodo.id ? savedTodo : todo )); } catch (error) { console.error(添加失败:, error); } }; return ( div h1待办事项/h1 AddTodoForm onAdd{addTodo} disabled{isPending} / ul {optimisticTodos.map(todo ( li key{todo.id} style{{ opacity: todo.optimistic ? 0.5 : 1 }} {todo.text} {todo.optimistic span (添加中...)/span} /li ))} /ul /div ); } function AddTodoForm({ onAdd, disabled }) { const [text, setText] useState(); const handleSubmit (e) { e.preventDefault(); if (text.trim()) { onAdd(text); setText(); } }; return ( form onSubmit{handleSubmit} input typetext value{text} onChange{(e) setText(e.target.value)} disabled{disabled} placeholder添加新任务... / button typesubmit disabled{disabled} {disabled ? 添加中... : 添加} /button /form ); }3.2 购物车示例import { useOptimistic, useState } from react; function ShoppingCart() { const [cart, setCart] useState([]); const [optimisticCart, addOptimisticItem] useOptimistic( cart, (state, newItem) { const existingItem state.find(item item.id newItem.id); if (existingItem) { return state.map(item item.id newItem.id ? { ...item, quantity: item.quantity newItem.quantity } : item ); } return [...state, newItem]; } ); const addToCart async (product, quantity 1) { const tempItem { id: product.id, name: product.name, price: product.price, quantity, isOptimistic: true, }; addOptimisticItem(tempItem); try { const response await fetch(/api/cart, { method: POST, body: JSON.stringify({ productId: product.id, quantity }), }); if (!response.ok) throw new Error(添加失败); const updatedCart await response.json(); setCart(updatedCart); } catch (error) { console.error(添加到购物车失败:, error); } }; const total optimisticCart.reduce( (sum, item) sum item.price * item.quantity, 0 ); return ( div h2购物车/h2 {optimisticCart.map(item ( div key{item.id} {item.name} x {item.quantity} ¥{item.price * item.quantity} {item.isOptimistic span (添加中...)/span} /div ))} div总计: ¥{total}/div /div ); }4. 错误处理和回滚4.1 显示错误状态function OptimisticWithErrorHandling() { const [items, setItems] useState([]); const [error, setError] useState(null); const [optimisticItems, addOptimisticItem] useOptimistic( items, (state, newItem) [...state, newItem] ); const addItem async (item) { setError(null); const tempItem { ...item, id: temp-${Date.now()}, isOptimistic: true }; addOptimisticItem(tempItem); try { const response await fetch(/api/items, { method: POST, body: JSON.stringify(item), }); if (!response.ok) throw new Error(添加失败); const savedItem await response.json(); setItems(prev prev.map(i i.id tempItem.id ? savedItem : i )); } catch (err) { setError(err.message); } }; return ( div {error div classNameerror{error}/div} {/* ... */} /div ); }5. 与 useTransition 配合import { useOptimistic, useTransition, useState } from react; function SearchComponent() { const [query, setQuery] useState(); const [results, setResults] useState([]); const [isPending, startTransition] useTransition(); const [optimisticResults, addOptimisticResult] useOptimistic( results, (state, newQuery) { return { ...state, loading: true, query: newQuery }; } ); const handleSearch (newQuery) { setQuery(newQuery); startTransition(async () { addOptimisticResult(newQuery); try { const response await fetch(/api/search?q${newQuery}); const data await response.json(); setResults(data); } catch (error) { console.error(搜索失败:, error); } }); }; return ( div input typetext value{query} onChange{(e) handleSearch(e.target.value)} placeholder搜索... / {isPending div搜索中.../div} /div ); }6. 最佳实践6.1 适合乐观更新的场景// ✅ 适合乐观更新的场景 // - 点赞、收藏、关注 // - 添加购物车 // - 发送消息 // - 发表评论 // - 切换开关 // ❌ 不适合乐观更新的场景 // - 支付操作 // - 删除重要数据可以但需要确认 // - 表单提交需要验证 // - 任何可能导致数据不一致的敏感操作6.2 添加视觉反馈.optimistic-item{opacity:0.7;animation:pulse 1s infinite;}keyframespulse{0%, 100%{opacity:0.7;}50%{opacity:1;}}7. 总结核心要点要点说明核心价值提升用户体验让界面响应更即时主要特性立即更新 UI失败后自动回滚适用场景点赞、评论、购物车等低风险操作React 19useOptimistic 是官方推荐方案记忆口诀乐观更新体验好立即响应不等待失败回滚自动做用户感知零延迟8. 相关资源React 19 useOptimistic 文档React 19 新特性