乐观更新是用户操作后先显示预期结果,再等待服务端确认。例如点亮收藏图标时立即改变外观,不必让用户盯着加载动画;请求失败则恢复相关状态并给出处理入口。它适合结果可预测、容易撤回的操作。付款到账、审批通过、库存锁定等结果由外部事实决定,不宜提前显示为完成。
设计的重点不是“让所有按钮都更快”,而是划清可以先显示的变化、尚未确认的事实、失败时需要撤回的范围。可以在墨刀原型中串联即时反馈与失败恢复,让团队在开发前看到一条操作从点击到确认的完整变化。

收藏图标可以先亮,“已同步”要等确认
假设产品经理在需求列表收藏“优化采购申请”。点击前是空心收藏图标,点击后立即点亮,读者可以接着查看下一条需求。如果此时显示“收藏已同步到所有设备”,就把本地显示当成了服务器事实:请求可能尚未发出,也可能被权限校验拒绝。
本例将反馈分成两层:收藏图标表达用户刚刚选择的状态;轻量的“同步中”文字表达当前是否已确认。确认后同步标记消失,长期显示已收藏状态即可,不必再弹一次强提示。等待拖长时应保留可见状态,避免用户把界面静止误认为没有点击成功。
这种分层也解释了乐观更新与普通加载的区别:加载反馈告诉人“系统收到操作”;乐观更新会先改变结果的呈现,例如列表新增一条记录或卡片移到另一列。React的useOptimistic文档将其描述为操作进行期间的临时状态,最终仍回到已经确认的数据。选用框架不等于已经解决了失败提示、连续操作和业务回滚。
先问失败能不能收回,再决定是否乐观
“接口通常很快”不能单独成为使用理由。用户看到结果后会不会继续做重要决定,比平均响应时间更值得考虑。收藏失败只需恢复一个收藏图标;转账后显示“对方已到账”,可能让用户离开或重复转账,两者风险不同。
| 操作 | 可以先呈现什么 | 需要等待什么 |
|---|---|---|
| 收藏需求、关注主题 | 收藏图标、选中状态;必要时附同步状态 | 服务端确认后再宣称跨设备同步 |
| 发布一条评论 | 用户自己的评论及“发送中”标记 | 成功确认后再移除标记;失败保留文字 |
| 移动看板任务 | 任务卡片的新位置及待确认标记 | 权限、版本与流程规则校验 |
| 付款、发货、审批通过 | 按钮反馈、处理中状态 | 实际支付、物流或审批结果,不预先宣告完成 |
删除也要分对象。移除个人收藏容易恢复;删除共享文件可能影响别人正在做的事。后者即使界面先隐藏,也要先确定软删除、撤销窗口、权限和后台结果。若撤回无法保证,宁可显示清楚的处理中状态,不把不可逆动作包装成轻巧动画。
把一次收藏写成完整的状态规则
为“优化采购申请”保留一份已确认状态,再叠加这次尚未确认的收藏意图。它们可以在画面上看起来相同,却不能在规则里混成一个值。这样请求被拒绝时,团队知道应撤回哪一项变化。
- 点击收藏:收藏图标点亮,当前卡片显示同步中;列表滚动位置和其他卡片保持原状。
- 确认成功:保留点亮状态,取消同步标记。其他入口若展示同一需求,应同步呈现同一个已确认结果。
- 明确失败:恢复这次操作之前的已确认状态,在卡片附近说明“收藏未同步,可重试”。不能仅闪过一条消息后让用户不知道哪个收藏图标失效。
- 重新发起:先确定原请求已失败或由去重机制识别,恢复待确认状态;重试不能把同一条需求重复放进收藏列表。

如果收藏列表同时新增了一条卡片,计数也加了1,失败时就要一起撤回卡片与这次计数变化。不要刷新整个列表,把用户正在看的位置带走。更不能把失败前保存的整张列表快照覆盖回去:等待期间,同事可能已经新增了其他需求。
评论的失败策略又不同。已输入的文字不该随请求失败消失,可保留为“未发送”,旁边提供“重试”和“编辑”。因此,“失败恢复旧值”不是适用于所有组件的一句通用规则;还要保护用户在本次动作中付出的输入。
连续点击时,最后收到的响应不一定最新
用户先收藏又取消收藏,记为操作A和操作B。B先返回成功,A后返回成功;若页面按响应到达顺序覆盖收藏图标,最终会重新点亮,与用户最后的取消意图相反。只在原型中画一张“成功页”,看不见这个问题。
团队可以从两种策略中选一种。操作低频、等待可接受时,在当前请求结束前暂时禁用同一操作,并保留同步反馈。频繁开关的场景则允许继续点击,记录最后想要的状态,把同一对象的请求串行处理或合并为最终目标。此时按钮是否可点、最后目标是什么、何时不再显示同步中,都要写进交互说明。
忽略旧响应,只解决了前端显示问题。如果服务端仍按到达顺序执行“切换一次”,最终数据可能依然错误。研发需要共同确定提交的是“设置为已收藏/未收藏”还是“切换”,并处理操作序号、版本或串行写入。不要让设计说明承诺客户端无权保证的最终状态。
局部回滚也必须识别操作:A失败时,不能无条件撤掉后来B已经确认的结果。更稳妥的表述是“移除A带来的待确认变化,基于最新已确认数据和仍在等待的操作重新显示”,而不是“所有失败都恢复点击前截图”。
超时不等于失败,重试不等于再做一次
请求超时意味着客户端没有及时得到结果,不能直接推出服务端没完成。如果创建评论已成功,但返回途中断网,用户再次提交可能出现两条相同评论。这时界面应表达“正在确认发送结果”,由系统查询已有结果,或使用同一操作标识进行幂等重试。
确认失败后再提供重试;无法确认时则把未确定的状态保留在当前对象上,让用户知道该动作尚未完成确认。一个通用红色“失败”提示会诱导重复点击,不如具体说明“暂未确认是否发送成功,正在检查”。具体文案必须对应实际实现,不能在没有状态查询的产品里写“正在检查”。
页面关闭后是否继续发送、重开后是否保留待确认记录,则属于持久化和恢复策略。如需设计这一部分,可接着看自动保存、失败重试与草稿恢复;不要把内存中的乐观显示当作已经保存的草稿。
让原型覆盖“反悔”和“没收到结果”
先在墨刀中完成同一张需求卡片的初始、待确认、确认成功、明确失败四个状态,保持名称、位置和内容一致。用交互连线演示点击后立即点亮,再分别连到成功和失败。产品与研发看完后,应能直接指出哪一刻只是显示变化、哪一刻已有服务端确认。
随后用三条任务走查:收藏后立即取消;请求超时后再次点击;等待期间另一条需求更新。观察收藏图标是否符合最后意图、是否出现重复记录、回滚是否误伤其他数据。屏幕阅读器的状态提示也应跟随当前操作,不能只靠颜色辨认失败或把焦点突然移到页面顶部。
交给研发的说明可以直接采用这段规则:“收藏图标立即更新,当前对象标为待确认;明确失败仅撤回该操作,保留其他变更;同一对象连续操作按约定串行或合并处理;超时先确认结果;成功确认后同步列表与计数。”原型负责把这些后果讲清,实际请求乱序、幂等和版本处理还需要在实现中测试。
完成这条收藏路径后,再把同样的判断用于评论、任务移动等操作。值得复用的是状态分层和局部恢复规则,不是把每个按钮都改成先显示成功。