React Query:用列表查询结果设置单个项缓存是否可行?
问题解答
这种做法不仅不是反模式,反而属于React Query官方推荐的「数据规范化」实践,刚好能解决你现在遇到的多列表同步更新复杂低效的问题。
为什么这是合理的
- React Query的缓存系统支持细粒度操作,你通过
setQueryData把列表里的单个Todo存入['todo', todo.id]独立缓存键,本质是把扁平化的列表数据拆成单条实体,和后端REST/GraphQL的实体设计思路一致。 - 这种方式让单个Todo组件直接依赖对应id的缓存,不用关心数据来自哪个列表查询,彻底避免了跨
['my-todo-list']、['all-todo-list']等多个列表重复更新的麻烦。
现有代码的优化点
你当前在useTodos的组件渲染逻辑里循环调用setQueryData,每次组件渲染都会执行,建议把这个逻辑移到useQuery的onSuccess回调中,只在列表数据成功获取时更新一次,避免不必要的重复操作:
export const useTodos = () => { const queryClient = useQueryClient() const todoQuery = useQuery( ['todo-list'], fetchTodos, { onSuccess: (data) => { data.todos?.forEach((todo) => { queryClient.setQueryData(['todo', todo.id], todo) }) } } ) return todoQuery }
另外,对于useTodoById,可以设置initialData从已有列表缓存中读取数据,避免列表已经加载完成后,单个Todo组件还显示loading状态:
export const useTodoById = (id) => { const queryClient = useQueryClient() return useQuery( ['todo', id], () => fetchTodoById(id), { staleTime: 10 * 60 * 1000, // 10分钟 initialData: () => { // 优先从已有的列表缓存中查找对应Todo作为初始数据 return queryClient.getQueryData(['todo-list'])?.todos?.find(todo => todo.id === id) || queryClient.getQueryData(['my-todo-list'])?.todos?.find(todo => todo.id === id) } } ) }
突变更新的优化思路
当标记Todo为“已完成”时,你只需要更新['todo', id]的缓存,再按需让相关列表查询失效即可,不用逐个更新所有列表:
const markTodoAsDone = async (id) => { const queryClient = useQueryClient() // 乐观更新单个Todo queryClient.setQueryData(['todo', id], old => ({...old, completed: true})) try { await updateTodoApi(id, {completed: true}) } catch (err) { // 更新失败回滚缓存 queryClient.setQueryData(['todo', id], old => ({...old, completed: false})) throw err } // 让所有Todo列表查询变为stale,下次渲染时自动刷新(根据业务需求选择是否开启) queryClient.invalidateQueries(['todo-list']) queryClient.invalidateQueries(['my-todo-list']) }
内容的提问来源于stack exchange,提问作者Jester2007
相关产品推荐
相关产品推荐

