完成后你将能够
能实现失败后恢复旧值、成功后以服务端为准,并能解释并发 Mutation 的显示策略。
先选择最小的乐观范围
如果临时结果只需显示在 Mutation 所在组件,用 variables 与 isPending 渲染半透明项目最简单,不会改缓存,也不需要回滚。多个界面都必须立刻看到变化时才直接更新缓存;跨组件可用 mutationKey 与 useMutationState 读取正在提交的变量。
缓存更新需要四步事务
onMutate 先取消会覆盖乐观值的相关重取,再保存旧值并不可变地写入草稿;返回的快照供 onError 回滚。onSettled 返回 invalidateQueries 的 Promise,让 Mutation 保持 pending,直到服务端事实重新进入缓存。
import { useMutation } from '@tanstack/react-query'
export function useRenameIssue(issueId: string) {
const key = ['issues', 'detail', issueId] as const
return useMutation({
mutationFn: (title: string) => renameIssue({ id: issueId, title }),
onMutate: async (title, context) => {
await context.client.cancelQueries({ queryKey: key })
const previous = context.client.getQueryData<Issue>(key)
context.client.setQueryData<Issue>(key, (issue) =>
issue ? { ...issue, title } : issue,
)
return { previous }
},
onError: (_error, _title, result, context) => {
if (result) context.client.setQueryData(key, result.previous)
},
onSettled: (_data, _error, _title, _result, context) =>
context.client.invalidateQueries({ queryKey: key }),
})
}并发时不要假装只有一次写入
同一 mutationKey 可能同时存在多次提交,useMutationState 返回数组;使用 submittedAt 作为临时标识。复杂列表过滤、排序或权限规则若难以在客户端精确重演,应缩小乐观范围或只显示 pending 项,最终仍由服务端响应与失效重新对齐。
把本课变成可验证的能力
乐观值在请求期间可见,失败恢复准确旧值,完成后缓存重新与服务端事实一致。
实现 Issue 标题乐观重命名,注入一次 409 失败并同时提交两次修改,观察回滚与并发临时状态。
分别测试成功、网络失败、业务冲突和并发提交;每条路径最终都应与服务端一致,Mutation pending 持续到重新验证完成。
- 未取消进行中的 refetch,导致旧响应覆盖乐观值。
- 原地修改缓存对象,破坏订阅更新与回滚快照。