SuperAPI AI 工具集

frontend-optimistic-mutations

前端与建站

查看项目

更新于 10-16 04:04

技能介绍

本技能描述的是“写入路径”的纪律——乐观更新、回滚、幂等、缓存一致性,而不是某个 UI 库或样式系统。它直接建立在 frontend-data-contracts 技能(写入通过类型化客户端)和 frontend-architecture 技能(mutation 位于 modules/{feature}/hooks/,由 key 工厂管理)之上。目标:一次写入感觉即时(服务器确认前 UI 即反映)、安全(失败时恢复到完全相同的先前状态,重试不会重复扣款)、并让缓存保持一致(详情视图与每个列表页一致)。三者同时满足,才是这门手艺。

一、五个核心思想 乐观生命周期是固定的:cancel → snapshot → patch →(error:roll back)→(settle:invalidate),每个乐观 mutation 都遵循同样的五个节拍。回滚要逐字还原:失败时恢复打补丁前获取的确切快照,而不是“最佳猜测”式重新推导;快照保存在 mutation 上下文中。幂等只生成一次,而非每次尝试生成:键在表单初始化(或首次意图)时创建,使网络重试重放原始服务器响应,而不是执行两次操作。缓存同步移动:状态变更会同时给详情缓存和包含该实体的每个列表页打补丁,避免各界面徽标不一致。服务器状态绝不进入客户端 store:乐观状态存在于查询缓存中,而非 Zustand/Redux;缓存是服务器数据的唯一事实来源(依据 frontend-architecture §4)。

二、何时乐观(何时不乐观) 高置信、低冲突写入(切换状态、点赞、标记已付、排序):乐观——立即打补丁,出错回滚。返回服务端生成的 id/编号/总额的创建:pending 状态,然后从服务器响应 setQueryData;可选的临时乐观行,成功时协调。破坏性或难以撤销的写入(级联删除、转账):先确认,然后乐观或 pending——绝不可静默乐观。用户暂时看不到结果的写入(后台任务):pending + toast,完成后失效;不做乐观补丁。乐观是用于你有信心成功的写入的 UX 工具;如果失败常见或撤销代价高,优先使用 pending 状态。

三、乐观生命周期(TanStack Query) 规范形态见文档中的 useInvoiceMutations 示例:onMutate 中依次 cancelQueries(防止迟到的 refetch 覆盖)、getQueryData 快照详情、setQueryData 打补丁详情,并遍历 getQueriesData 快照并补丁每个列表页;onError 中按 context 逐字回滚详情与所有列表并展示类型化 ApiError;onSettled 中同时失效详情与列表,使服务端计算的字段(paidAt、聚合)重新拉取。为什么每一步:cancel——否则已在途的查询可能在补丁后解析并覆盖乐观状态;snapshot——唯一安全的回滚来源,绝不手工重建先前状态;patch——即时 UX,详情与列表一起改(§4);roll back——逐字恢复快照,然后暴露类型化 ApiError;settle 时 invalidate——无论成功或失败都重取,使服务端计算字段权威,因为失败的写入也可能已改变服务端状态。

四、非乐观写入:带服务端字段的创建 返回 id/编号/总额的创建无法完全乐观。将其作为 pending mutation 运行,并从响应回填缓存:onSuccess 中 setQueryData 种入详情,并 invalidateQueries 刷新列表;onError 通知错误。

五、缓存一致性(详情与列表同步) 单一实体会出现在许多缓存中:它的详情,以及每个筛选/分页的列表页。乐观补丁必须触及全部,否则各界面会不一致。使用层级 key 工厂(来自 frontend-architecture §4.3)精确目标:invoiceKeys 包含 all、lists、list(p)、detail(id)。getQueriesData({ queryKey: invoiceKeys.lists() }) 枚举每个已缓存列表页以便逐个打补丁;invalidateQueries({ queryKey: invoiceKeys.lists() }) 在结算时刷新全部;detail(id) 精确目标单个实体。为你触及的每一页按确切查询键做快照,使回滚逐字恢复每一页,而不仅是当前屏幕上的那一页。

六、幂等(资金类写入的安全重试) 重试的 POST 绝不能执行两次操作。在表单初始化(或首次用户意图)时生成一次键,贯穿重试携带,由客户端作为 header 发送;服务器在其窗口内对重复键重放原始响应。硬性规则:在意图时生成键,而不是在 mutationFn 内部(后者每次重试都会重跑,从而失去意义);数据客户端自动检测财务路由并注入 header,显式键始终优先以便重试重放;将幂等与 pending 时禁用 UI 配对,使用户无法发起第二次不同的写入。

七、重试策略 读:以退避重试若干次(多数查询库默认)——安全且幂等。写:不自动重试非幂等 mutation。仅在幂等键保证重放时重试,或仅在网络错误(状态 0)时重试,绝不在 4xx 时重试。冲突(409):不要重试——暴露类型化错误、失效,让用户基于新数据重新决定。示例:retry: (count, error) => error.isNetworkError && count < 2。

八、库适配器 五拍生命周期相同,钩子不同。TanStack Query:onMutate(cancel + snapshot + patch)→ onError(rollback)→ onSettled(invalidate)。RTK Query:onQueryStarted 中 updateQueryData 返回 patchResult;await queryFulfilled 并在 catch 中调用 patchResult.undo();结算时 invalidatesTags。SWR:mutate(key, optimisticData, { rollbackOnError: true, populateCache, revalidate: true })。对于 React Native,三者均无需改动即可工作;native 上缓存同样是唯一事实来源。保持 mutation 钩子不依赖 DOM,以便在 web 与 native 间共享。

九、约定检查清单(评审时强制) 乐观 mutation 遵循 cancel → snapshot → patch → rollback → invalidate;onMutate 在打补丁前取消在途查询;回滚从 context 恢复确切快照,而非重新推导;详情与每个受影响的列表页一起打补丁和快照;onSettled 在成功与失败时都失效以重取服务端计算字段;幂等键在意图时生成并跨重试重放,而非每次尝试重新生成;涉及资金/破坏性写入先确认,并在 pending 时禁用触发器;非幂等写入不自动重试,409 暴露而非重试;服务器状态留在查询缓存中,绝不复制进客户端 store;查询键来自层级工厂,失效是限定范围的,而非全局粗暴失效。

十、如何应用本技能 新增乐观 mutation:判断乐观是否安全(§1)。写五拍(§2)。找出该实体所在的所有缓存并全部打补丁/快照(§4)。使写入安全可重试:在表单初始化时生成幂等键,贯穿 mutation,确认客户端发送它(§5),并设置仅网络的有界重试(§6)。调试闪烁/写入后状态错误:检查 onMutate 是否取消查询(迟到 refetch 覆盖)以及 onSettled 是否失效(陈旧的服务端计算字段);检查是否所有列表页都打了补丁,而不仅是可见的那一页。评审写入路径:运行 §8 的检查清单。价值最高的捕获点是缺失 cancelQueries(竞态覆盖)、部分缓存补丁(详情/列表不一致)、以及在 mutationFn 内部生成幂等键(不再保护重试)。

十一、发布/安装 本技能遵循 Anthropic SKILL.md 格式,可跨 agent 移植。放在公开 GitHub 仓库的 skills/frontend-optimistic-mutations/SKILL.md 下。保留 frontmatter 的 name 和高信号 description——发现索引会据此匹配。安装命令:npx skills add <org>/<repo> --skill "frontend-optimistic-mutations"。非 SKILL.md 的 agent 可从 AGENTS.md / CLAUDE.md 指向此处;Kiro 可将其镜像为 steering 文件。