写入、乐观更新与回滚
写入、乐观更新与回滚 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
读完你能为「收藏 / 点赞」这类写操作实现完整闭环:立即改界面(乐观)、请求确认、失败回滚;能处理快速连点与并发响应,并用「界面态 vs 服务端态」的对照验证最终一致。
问题场景
课程卡片的收藏星标:点一下,请求 800ms 后成功。这 800ms 里用户得不到任何反馈,于是再点两下——三个请求排队,收藏状态翻转三次,最终结果取决于服务器处理顺序,连用户自己都说不清到底收藏了没有。写操作的两难:等服务器,体验差;不等服务器,就得自己保证「错了能改回来」。
核心解释
乐观更新的标准闭环:乐观写 → 服务端确认 → 失败回滚,加上连点防抖:
async function toggleFav(id) {
if (pending[id]) return; // 连点防抖:该对象处理中,忽略
const nextValue = !favs[id]; // 本次意图
setFavs((f) => ({ ...f, [id]: nextValue })); // 1. 乐观写:界面立刻变
setPending((p) => ({ ...p, [id]: true }));
try {
const server = await setFavoriteApi(id, nextValue); // 2. 服务端确认
setFavs((f) => ({ ...f, [id]: server })); // 3. 以服务器返回校准
} catch (e) {
setFavs((f) => ({ ...f, [id]: !nextValue })); // 4. 失败:只回滚本键
setError("收藏失败,已恢复");
} finally {
setPending((p) => ({ ...p, [id]: false }));
}
}
两个容易出错的细节。第一,回滚要最小化:只恢复本次操作改的那个键,不能把用户期间的其他操作一起回滚——所以用函数式更新,按对象逐键恢复。第二,并发响应的最终一致:多个对象的请求并行时,每个响应只校准自己的键,互不覆盖:
// 并发响应落地:每个响应只校准自己那门课的键
setUiFavs((f) => ({ ...f, [resp.id]: resp.value }));
setServerFavs((s) => ({ ...s, [resp.id]: resp.value }));
若服务器返回整份列表,就要警惕「旧响应后到覆盖新状态」——这是请求竞态在写操作里的变体,解法同样是序号守卫。React 18 也提供了 useOptimistic 把「乐观值 + 自动回滚」封装成原语,底层思路与手写一致。验证方式:把「界面收藏集」和「服务器收藏集」并排显示,连点、开失败模式,观察两侧从分歧到一致的全过程。
示例拆解
实验区 6 门课程可收藏,每门课请求延迟 800ms:
- 每张卡片显示星标(界面态)与处理中标记;右侧面板显示服务器实际收到的收藏集与两侧一致性;
- 连点同一门课 3 次:处理中被锁定,只发一个请求;
- 开「模拟失败」后点收藏:星标先亮,800ms 后弹回,提示「收藏失败,已恢复」,服务器集合不变;
- 关掉失败模式,连点不同课程:每个响应只校准自己的键,最终两侧一致;
- 验证:交替执行上述操作,盯住「界面态与服务端态」的对照指示。
动手任务
打开本课实验区:
- 把「处理中忽略连点」改成「排队执行」:上一次完成后自动补发最新意图,测试连点 3 次的最终结果。
- 给回滚加重试:失败恢复后自动再试一次(最多 1 次),验证最终一致。
- 把两门课的延迟改成随机(300-1000ms),快速操作后确认没有出现旧响应覆盖新状态;说明你的校准逻辑为什么安全。
常见错误
- 乐观写后不校准:以为界面写了就完事,服务器返回的最终值(可能被别人改过)没有落地。
- 回滚直接清空整份状态:把用户期间的其他操作一起回滚了;回滚只针对本次操作影响的那部分。
- 连点不设防:同一对象堆 N 个请求,最终状态等于最后一次响应,而不是用户最后一次点击的意图。
- 用整份服务器响应直接覆盖:乱序响应会覆盖较新的本地状态;按响应与对象的对应关系校准,过期响应丢弃。
检查与复盘
自查清单:
- 能按「乐观写 → 确认 → 回滚」复述闭环,说出每一步对应哪行代码
- 连点同一课程只发一个请求(或排队),验证过
- 失败回滚后界面态与服务器态重新一致,验证过
参考资料
- React 官方文档:useOptimistic —— 乐观更新的官方原语
- React 官方文档:useTransition —— 乐观更新常见的配合者
- React 官方文档:管理状态 —— 请求后更新状态的完整语境
读完这一课了?
标记完成后,工作台会安排接下来的学习。