TRPC+Prisma+NextJS Todo应用:如何简化单个Todo的乐观更新?
简化TRPC Todo应用的乐观更新逻辑
问题背景
你当前通过深拷贝整个Todo列表再遍历修改的方式实现乐观更新,希望简化逻辑,像React Query那样直接定位单个Todo项修改缓存,避免全量更新。
解决方案
方案1:直接用TRPC底层的React Query queryClient操作缓存
TRPC基于React Query实现,所以可以直接获取queryClient,用函数式更新修改列表中的单个项,无需全量拷贝:
import { useQueryClient } from '@tanstack/react-query'; import { api } from '../utils/trpc'; // 替换成你的TRPC客户端路径 const useToggleTodo = () => { const queryClient = useQueryClient(); const toggleMutation = api.todo.toggle.useMutation({ // 乐观更新触发时 onMutate: async ({ id, completed }) => { // 取消正在进行的Todo列表查询,防止覆盖乐观更新 await queryClient.cancelQueries({ queryKey: ['todo.getAll'] }); // 缓存当前Todo列表的快照,用于失败回滚 const previousTodos = queryClient.getQueryData<Todo[]>(['todo.getAll']); // 直接修改缓存中对应ID的Todo状态 queryClient.setQueryData(['todo.getAll'], (oldTodos) => { if (!oldTodos) return []; return oldTodos.map(todo => todo.id === id ? { ...todo, completed } : todo ); }); // 返回上下文,供错误处理时回滚 return { previousTodos }; }, // 当mutation失败时,回滚缓存 onError: (_err, _vars, context) => { if (context?.previousTodos) { queryClient.setQueryData(['todo.getAll'], context.previousTodos); } }, // 无论成功失败,最终刷新缓存确保和后端一致(可选) onSettled: () => { queryClient.invalidateQueries({ queryKey: ['todo.getAll'] }); }, }); return toggleMutation; };
方案2:优化你现有的TRPC ctx操作逻辑
如果坚持用TRPC的ctx方法,也可以去掉低效的JSON深拷贝,用对象展开运算符直接生成新数组:
// 获取当前缓存的Todo列表 const prevData = ctx.todos.getAll.getData(); if (!prevData) return; // 直接生成更新后的数组,无需深拷贝整个列表 const updatedTodos = prevData.map(todo => todo.id === targetId ? { ...todo, completed: newCompletedState } : todo ); // 更新缓存 ctx.todos.getAll.setData(undefined, updatedTodos);
关键注意事项
- QueryKey必须一致:确保你操作的
queryKey和获取Todo列表时的完全匹配(TRPC自动生成的queryKey格式为[路由名.方法名],比如['todo.getAll'])。 - 避免JSON深拷贝:JSON序列化的深拷贝性能差且会丢失特殊类型,用对象展开或结构化克隆更安全高效。
- 必须处理回滚:mutation失败时一定要恢复缓存到之前的状态,避免数据不一致。
内容的提问来源于stack exchange,提问作者José Pino
相关产品推荐
相关产品推荐

