Skip to content
返回设计中心

乐观 UI 更新

立即显示预期结果并在服务器响应时对账。

UX 模式进阶

为什么

等 300ms 点赞切换的服务器往返感迟。乐观 UI 即时应用变更,错误时回滚 — 感知性能改善即使网络慢。

Nielsen Norman Group 研究显示用户在离开前平均停留 10-15 秒。清晰的视觉层次和渐进式披露将任务完成率提高 40-60%。

何时使用

用于低风险、易可逆动作:点赞、收藏、已读/未读、勾选。避免不可逆动作(删除、支付)或陈旧数据致害(余额、库存)。

如何做

1. 本地应用 — 请求前即时变更缓存/UI 到预期状态 2. 发请求 — 后台触发 POST/PUT 3. 成功 — 对账:服务器可能返回规范状态(如新点赞数),替换本地 4. 错误 — 回滚到前状态,显 toast("无法点赞 — 重试") 5. 冲突 — 服务器说状态已变则重取并显真相 6. 库 — React Query 的 onMutate + onError 回滚、SWR 的 optimisticData、或手卷 7. 幂等 — 使动作幂等(点赞切换非点赞递增)以让重试安全

示例

// React Query — 乐观点赞切换 useMutation({ mutationFn: (liked: boolean) => api.put(`/posts/${id}/like`, { liked }), onMutate: async (liked) => { await queryClient.cancelQueries({ queryKey: ["post", id] }); const prev = queryClient.getQueryData(["post", id]); queryClient.setQueryData(["post", id], (p) => ({ ...p, liked })); // 乐观 return { prev }; // 回滚上下文 }, onError: (_e, _v, ctx) => { queryClient.setQueryData(["post", id], ctx?.prev); // 回滚 toast.error("无法更新 — 已回滚"); }, onSettled: () => queryClient.invalidateQueries({ queryKey: ["post", id] }), }); // 手卷 — 同模式 const [liked, setLiked] = useState(post.liked); const toggle = async () => { const prev = liked; setLiked(!prev); // 乐观 try { await api.put(`/posts/${id}/like`, { liked: !prev }); } catch { setLiked(prev); toast.error("已回滚"); } // 回滚 };

常见问题

  • ⚠错误无回滚 — UI 永显乐观状态,与服务器发散
  • ⚠用于不可逆动作 — 用户乐观 '删除',服务器失败,UI 数据无但服务器有
  • ⚠非幂等动作重试 — 重试双点赞,服务器数错

最佳实践

  • ✓始终提供 onError 回滚 — 请求失败时乐观状态须回退
  • ✓仅用于可逆动作 — 点赞、收藏、切换;绝不乐观删除或支付
  • ✓使动作幂等 — 点赞切换(非递增)以让重试不双计

常见问题解答