
配图源自 Freepik
本文以 V5 版本为例
Query Key
它是由字符串、(可嵌套)对象、或两者兼具组成的数组。
顺序
数组的顺序很重要
// 以下 Query Key 不相同
useQuery({ queryKey: ['todos', status, page] })
useQuery({ queryKey: ['todos', page, status] })
useQuery({ queryKey: ['todos', undefined, page, status] })
对象的顺序不重要
// 以下 Query Key 相同
useQuery({ queryKey: ['todos', { status, page }] })
useQuery({ queryKey: ['todos', { page, status }] })
useQuery({ queryKey: ['todos', { page, status, other: undefined }] })
对象总是以确定性的 sort 排序:👇
/**
* Default query & mutation keys hash function.
* Hashes the value into a stable hash.
*/
export function hashKey(queryKey: QueryKey | MutationKey): string {
return JSON.stringify(queryKey, (_, val) =>
isPlainObject(val)
? Object.keys(val)
.sort()
.reduce((result, key) => {
result[key] = val[key]
return result
}, {} as any)
: val,
)
}
结构化
尽管 Query Key 在整个 Query 中唯一便可使用,但我们可以按一定的颗粒度进行划分。比如:
useQuery({ queryKey: ['todos', 'list', { filters: 'all' }] })
useQuery({ queryKey: ['todos', 'list', { filters: 'done' }] })
useQuery({ queryKey: ['todos', 'detail', 1] })
useQuery({ queryKey: ['todos', 'detail', 2] })
这样做的好处是,在处理数据更新时更加灵活,或者批量使对应缓存失效:
function useUpdateTitle() {
return useMutation({
mutationFn: updateTitle,
onSuccess: newTodo => {
// 更新单个数据
queryClient.setQueryData(['todos', 'detail', newTodo.id], newTodo)
// 更新列表数据
queryClient.setQueriesData(['todos', 'list'], previous =>
previous.map(todo => (todo.id === newTodo.id ? newTodo : todo))
)
// 使查询列表失效
queryClient.invalidateQueries({queryKey: ['todos', 'list']})
},
})
}
Related link: https://tkdodo.eu/blog/effective-react-query-keys#structure
有时,使用“纯对象”的 Query Key 更好:
function useTodosQuery(filter: string) {
return useQuery({
// queryKey: ["todos", "list", filter],
queryKey: [{scope: 'todos', entity: 'list', filter}],
queryFn: ({queryKey}) => {
// 保不准下次会新增什么参数,此时对象要优于数组
// const [, , filter] = queryKey;
const {filter} = queryKey
return fetchTodos({filter})
},
})
}
Related Link: https://tkdodo.eu/blog/leveraging-the-query-function-context#object-query-keys
Select
在 useQuery 或 useInfinityQuery 中的 select 会在每次组件更新时执行,而不是查询结果变化时才执行,所以按需使用 useCallback(但如果处理不会很复杂,也不需要缓存处理)。