Loading...
Loading...
Compare original and translation side by side
statusasyncStatusstatusasyncStatusstatus'pending' | 'success' | 'error'asyncStatus'loading' | 'idle'status === 'pending'asyncStatus === 'loading' && status === 'success'status'pending' | 'success' | 'error'asyncStatus'loading' | 'idle'status === 'pending'asyncStatus === 'loading' && status === 'success'refresh()refetch()refresh()refetch()refresh()staleTimerefetch()refresh()staleTimerefetch()query()export const productKeys = {
root: () => ['products'] as const,
list: (params: { q?: string; page?: number }) => ['products', 'list', params] as const,
detail: (id: string) => ['products', 'detail', id] as const,
}as constkey: () => productKeys.detail(id.value)query()query()export const productKeys = {
root: () => ['products'] as const,
list: (params: { q?: string; page?: number }) => ['products', 'list', params] as const,
detail: (id: string) => ['products', 'detail', id] as const,
}as constkey: () => productKeys.detail(id.value)query()useQuery()useQuery()const productQuery = useQuery(() => ({
key: productKeys.detail(route.params.id as string),
enabled: !!route.params.id,
query: () => productService.getById(route.params.id as string),
}))const productQuery = useQuery(() => ({
key: productKeys.detail(route.params.id as string),
enabled: !!route.params.id,
query: () => productService.getById(route.params.id as string),
}))defineQueryOptions()defineQueryOptions()export const productDetailQuery = defineQueryOptions((id: string) => ({
key: productKeys.detail(id),
query: () => productService.getById(id),
}))
// Usage
const q = useQuery(() => ({ ...productDetailQuery(id.value), enabled: !!id.value }))export const productDetailQuery = defineQueryOptions((id: string) => ({
key: productKeys.detail(id),
query: () => productService.getById(id),
}))
// 使用示例
const q = useQuery(() => ({ ...productDetailQuery(id.value), enabled: !!id.value }))defineQuery()defineQuery()const queryCache = useQueryCache()
const updateProduct = useMutation(() => ({
mutation: (vars: { id: string; name: string }) => productService.update(vars),
onSuccess: async (data, vars) => {
await queryCache.invalidateQueries({ key: productKeys.detail(vars.id) })
await queryCache.invalidateQueries({ key: productKeys.root() })
},
}))invalidateQueriesconst queryCache = useQueryCache()
const updateProduct = useMutation(() => ({
mutation: (vars: { id: string; name: string }) => productService.update(vars),
onSuccess: async (data, vars) => {
await queryCache.invalidateQueries({ key: productKeys.detail(vars.id) })
await queryCache.invalidateQueries({ key: productKeys.root() })
},
}))invalidateQueries| Action | Invalidate |
|---|---|
| Create | |
| Update | |
| Delete | Lists, optionally remove detail |
| 操作 | 需失效的查询 |
|---|---|
| 新增 | |
| 更新 | |
| 删除 | 列表查询,可选择性移除详情查询 |
const page = ref(1)
const listQuery = useQuery(() => ({
key: productKeys.list({ q: search.value, page: page.value }),
query: () => productService.list({ q: search.value, page: page.value }),
placeholderData: (prev) => prev, // Keep previous page visible
}))const page = ref(1)
const listQuery = useQuery(() => ({
key: productKeys.list({ q: search.value, page: page.value }),
query: () => productService.list({ q: search.value, page: page.value }),
placeholderData: (prev) => prev, // 保持显示上一页内容
}))mutation.variablesmutation.variablesconst mutate = useMutation(() => ({
onMutate: async (vars) => {
const key = productKeys.detail(vars.id)
await queryCache.cancelQueries({ key })
const oldValue = queryCache.getQueryData(key)
const newValue = oldValue ? { ...oldValue, ...vars } : oldValue
queryCache.setQueryData(key, newValue)
return { key, oldValue, newValue }
},
mutation: (vars) => productService.update(vars),
onError: (err, vars, ctx) => {
if (!ctx) return
// Race-safe rollback
if (queryCache.getQueryData(ctx.key) === ctx.newValue) {
queryCache.setQueryData(ctx.key, ctx.oldValue)
}
},
onSettled: async (data, err, vars, ctx) => {
if (ctx) await queryCache.invalidateQueries({ key: ctx.key })
},
}))const mutate = useMutation(() => ({
onMutate: async (vars) => {
const key = productKeys.detail(vars.id)
await queryCache.cancelQueries({ key })
const oldValue = queryCache.getQueryData(key)
const newValue = oldValue ? { ...oldValue, ...vars } : oldValue
queryCache.setQueryData(key, newValue)
return { key, oldValue, newValue }
},
mutation: (vars) => productService.update(vars),
onError: (err, vars, ctx) => {
if (!ctx) return
// 竞态安全的回滚
if (queryCache.getQueryData(ctx.key) === ctx.newValue) {
queryCache.setQueryData(ctx.key, ctx.oldValue)
}
},
onSettled: async (data, err, vars, ctx) => {
if (ctx) await queryCache.invalidateQueries({ key: ctx.key })
},
}))useQueryCache()useQueryCache()| Problem | Solution |
|---|---|
| Query doesn't refetch when X changes | Include X in the key, use |
| Invalidation doesn't work | Check key hierarchy, use exact matching or predicate |
| Old data while loading | Expected behavior — |
| Optimistic rollback bugs | Cancel queries before cache write, use race-safe rollback |
| 问题 | 解决方案 |
|---|---|
| 当X变化时查询未重新获取数据 | 将X包含在键中,使用 |
| 缓存失效不生效 | 检查键的层级结构,使用精确匹配或断言函数 |
| 加载时显示旧数据 | 这是预期行为—— |
| 乐观更新回滚出现bug | 在写入缓存前取消查询,使用竞态安全的回滚机制 |
useQuerydefineQueryOptionsuseMutationstatusasyncStatususeQuerydefineQueryOptionsuseMutationstatusasyncStatus