乐观更新
React Query 提供了两种方式在 mutation
完成之前提前更新 UI:
通过 UI 直接更新(适用于 mutation 和查询在同一个组件中)。
通过
onMutate
直接更新缓存(适用于多个组件共享数据)。
方式 1:通过 UI 直接更新
基本示例
const { isPending, variables, mutate, isError } = useMutation({
mutationFn: (newTodo: string) => axios.post("/api/data", { text: newTodo }),
onSettled: async () => {
return await queryClient.invalidateQueries({ queryKey: ["todos"] });
},
});
function App() {
return (
<ul>
{todoQuery.items.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
{isPending && <li style={{ opacity: 0.5 }}>{variables}</li>}
</ul>
);
}
思路
isPending
为true
时,UI 先显示一个半透明的临时 todo。mutation
成功后,queryClient.invalidateQueries
触发查询刷新,新的 todo 变为正常状态。mutation
失败时,临时 todo 自动消失(或者你可以提供一个重试按钮)。
处理失败回滚
如果 mutation
失败,可以使用 isError
提供重试选项:
{
isError && (
<li style={{ color: "red" }}>
{variables}
<button onClick={() => mutate(variables)}>Retry</button>
</li>
);
}
方式 2:直接更新缓存
如果多个组件依赖同一个数据,UI 方式不足够,需要直接更新 queryClient
。
添加 todo
的乐观更新
todo
的乐观更新const queryClient = useQueryClient();
useMutation({
mutationFn: addTodo,
onMutate: async (newTodo) => {
// 取消当前正在进行的查询,避免被新的请求覆盖
await queryClient.cancelQueries({ queryKey: ["todos"] });
// 备份当前数据(快照)
const previousTodos = queryClient.getQueryData(["todos"]);
// 直接更新 UI,先插入新 todo
queryClient.setQueryData(["todos"], (old) => [...old, newTodo]);
// 返回回滚数据
return { previousTodos };
},
// 失败时回滚
onError: (err, newTodo, context) => {
queryClient.setQueryData(["todos"], context.previousTodos);
},
// 成功或失败后都重新拉取数据
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["todos"] });
},
});
思路
onMutate
先取消正在进行的
todos
请求。备份原始
todos
数据,防止 mutation 失败。立即插入新 todo,提高 UI 响应速度。
onError
如果 mutation 失败,则回滚
todos
,恢复原始状态。
onSettled
成功或失败后,都重新拉取
todos
数据,确保和服务器数据一致。
更新单个 todo
的乐观更新
todo
的乐观更新useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
await queryClient.cancelQueries({ queryKey: ["todos", newTodo.id] });
const previousTodo = queryClient.getQueryData(["todos", newTodo.id]);
queryClient.setQueryData(["todos", newTodo.id], newTodo);
return { previousTodo, newTodo };
},
onError: (err, newTodo, context) => {
queryClient.setQueryData(["todos", context.newTodo.id], context.previousTodo);
},
onSettled: (newTodo) => {
queryClient.invalidateQueries({ queryKey: ["todos", newTodo.id] });
},
});
💡 思路
直接更新某个
todo
,mutation 成功后不会重新获取数据,提高性能。失败时回滚数据,防止 UI 变成不一致状态。
如何选择合适的方式?
方案
适用场景
优势
缺点
通过 UI 直接更新
只在当前组件显示临时数据
简单,代码量少
不能同步多个组件的数据
通过缓存 onMutate
更新
需要多个组件同步 UI
无需 refetch,UI 立即响应
需要手动管理数据回滚
一般来说:
UI 更新方式适用于局部 UI 交互,比如提交表单。
缓存更新方式适用于全局状态,如
todos
被多个组件共享时。
最后更新于
这有帮助吗?