- 使
queryKey
与queryFn
强相关 - 以类型安全的方式管理
queryKey
- 让
queryClient
的操作更清楚地关联到哪个自定义 ReactQuery hook - 为自定义 ReactQuery hook 设置默认选项更容易和更清晰
- 中间件
English | 简体中文
$ npm i react-query-kit
# or
$ yarn add react-query-kit
import { QueryClient, dehydrate } from '@tanstack/react-query'
import { createQuery } from 'react-query-kit'
type Response = { title: string; content: string }
type Variables = { id: number }
const usePost = createQuery<Response, Variables, Error>({
primaryKey: '/posts',
queryFn: ({ queryKey: [primaryKey, variables] }) => {
// primaryKey 相等于 '/posts'
return fetch(`${primaryKey}/${variables.id}`).then(res => res.json())
},
// 你还可以通过中间件来定制这个 hook 的行为
use: [myMiddleware]
})
const variables = { id: 1 }
// example
export default function Page() {
// queryKey 相等于 ['/posts', { id: 1 }]
const { data } = usePost({ variables })
return (
<div>
<div>{data?.title}</div>
<div>{data?.content}</div>
</div>
)
}
console.log(usePost.getKey()) // ['/posts']
console.log(usePost.getKey(variables)) // ['/posts', { id: 1 }]
// nextjs 例子
export async function getStaticProps() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery(usePost.getFetchOptions(variables))
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
}
// 在 react 组件外使用
const data = await queryClient.fetchQuery(
usePost.getFetchOptions(variables)
)
// useQueries 例子
const queries = useQueries({
queries: [
usePost.getOptions(variables),
useUser.getOptions(),
],
})
// getQueryData
queryClient.getQueryData(usePost.getKey(variables)) // Response
// setQueryData
queryClient.setQueryData(usePost.getKey(variables), {...})
Options
primaryKey: string
- 必填
primaryKey
将是queryKey
数组的第一个元素
use: Middleware[]
- 可选
- 中间件函数数组 (详情)
Expose Methods
getPrimaryKey: () => primaryKey
getKey: (variables: TVariables) => [primaryKey, variables]
queryFn: QueryFunction<TFnData, [primaryKey, TVariables]>
queryKeyHashFn: (queryKey: [primaryKey, TVariables]) => string
getOptions: (variables: TVariables) => UseInfiniteQueryOptions
getFetchOptions: (variables: TVariables) => ({ queryKey, queryFn, queryKeyHashFn })
import { QueryClient, dehydrate } from '@tanstack/react-query'
import { createInfiniteQuery } from 'react-query-kit'
type Response = { projects: { id: string; name: string }[]; nextCursor: number }
type Variables = { active: boolean }
const useProjects = createInfiniteQuery<Response, Variables, Error>({
primaryKey: 'projects',
queryFn: ({ queryKey: [_primaryKey, variables], pageParam }) => {
return fetch(
`/projects?cursor=${pageParam}?active=${variables.active}`
).then(res => res.json())
},
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
defaultPageParam: 1,
})
const variables = { active: true }
// example
export default function Page() {
// queryKey equals to ['projects', { active: true }]
const { data, fetchNextPage, hasNextPage, isFetching, isFetchingNextPage } =
useProjects({ variables })
return (
<div>
{data.pages.map((group, i) => (
<React.Fragment key={i}>
{group.projects.map(project => (
<p key={project.id}>{project.name}</p>
))}
</React.Fragment>
))}
<div>
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? 'Loading more...'
: hasNextPage
? 'Load More'
: 'Nothing more to load'}
</button>
</div>
<div>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</div>
</div>
)
}
// nextjs example
export async function getStaticProps() {
const queryClient = new QueryClient()
await queryClient.prefetchInfiniteQuery(
useProjects.getFetchOptions(variables)
)
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
}
// 在 react 组件外使用
const data = await queryClient.fetchInfiniteQuery(
useProjects.getFetchOptions(variables)
)
Options
primaryKey: string
- 必填
primaryKey
将是queryKey
数组的第一个元素
use: Middleware[]
- 可选
- 中间件函数数组 (详情)
Expose Methods
getPrimaryKey: () => primaryKey
getKey: (variables: TVariables) => [primaryKey, variables]
queryFn: QueryFunction<TFnData, [primaryKey, TVariables]>
queryKeyHashFn: (queryKey: [primaryKey, TVariables]) => string
getOptions: (variables: TVariables) => UseInfiniteQueryOptions
getFetchOptions: (variables: TVariables) => ({ queryKey, queryFn, queryKeyHashFn, getNextPageParam, getPreviousPageParam, initialPageParam })
这与在查询配置中将 suspense 选项设置为 true 具有相同的效果,但在 TypeScript 的体验更好,因为 data 是有定义的(因为错误和加载状态由 Suspense 和 ErrorBoundaries 处理)。
import { createSuspenseQuery } from 'react-query-kit'
createSuspenseQuery({
...options,
})
// 相当于
createQuery({
...options,
enabled: true,
suspense: true,
throwOnError: true,
})
import { createSuspenseInfiniteQuery } from 'react-query-kit'
createSuspenseInfiniteQuery({
...options,
})
// 相当于
createInfiniteQuery({
...options,
enabled: true,
suspense: true,
throwOnError: true,
})
import { createMutation } from 'react-query-kit'
const useAddTodo = createMutation(
async (variables: { title: string; content: string }) =>
fetch('/post', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(variables),
}).then(res => res.json()),
{
onSuccess(data, variables, context) {
// do somethings
},
}
)
function App() {
const mutation = useAddTodo({
onSettled: (data, error, variables, context) => {
// Error or success... doesn't matter!
},
})
return (
<div>
{mutation.isPending ? (
'Adding todo...'
) : (
<>
{mutation.isError ? (
<div>An error occurred: {mutation.error.message}</div>
) : null}
{mutation.isSuccess ? <div>Todo added!</div> : null}
<button
onClick={() => {
mutation.mutate({ title: 'Do Laundry', content: 'content...' })
}}
>
Create Todo
</button>
</>
)}
</div>
)
}
// usage outside of react component
useAddTodo.mutationFn({ title: 'Do Laundry', content: 'content...' })
Options
use: Middleware[]
- 可选
- 中间件函数数组 (详情)
Returns
getKey: () => MutationKey
mutationFn: MutationFunction<TData, TVariables>
此功能的灵感来自于 SWR 的中间件功能。
中间件接收 hook,可以在运行它之前和之后执行逻辑。如果有多个中间件,则每个中间件包装下一个中间件。列表中的最后一个中间件将接收原始的 hook。
import { QueryClient } from '@tanstack/react-query'
import { Middleware, MutationHook, QueryHook, getKey } from 'react-query-kit'
const myMiddleware: Middleware<
QueryHook<Response, Variables>
> = useQueryNext => {
return options => {
const { userId } = useAuth()
const client = useQueryClient()
const variables = options.variables ?? { id: userId }
const hasData = () => !!client.getQueryData(useUser.getKey(variables))
return useQueryNext({
...options,
variables,
enabled: options.enabled ?? !hasData(),
})
}
}
const useUser = createQuery<Response, Variables>({
// ...
use: [
myMiddleware,
// 或者直接定义在 `use` 数组内
function myMiddleware2(useQueryNext) {
return options => {
// ...
return useQueryNext(options)
}
},
],
})
// 全局中间件
const queryMiddleware: Middleware<QueryHook> = useQueryNext => {
return options => {
// 你还可以通过函数 getKey 获取 queryKey
const queryKey = getKey(options.primaryKey, options.variables)
// ...
return useQueryNext(options)
}
}
const mutationMiddleware: Middleware<MutationHook> = useMutationNext => {
return options => {
// ...
return useMutationNext(options)
}
}
const queryClient = new QueryClient({
defaultOptions: {
queries: {
use: [queryMiddleware],
},
mutations: {
use: [mutationMiddleware],
},
},
})
中间件将从上级合并。例如:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
use: [a],
},
},
})
const useSomething = createQuery({
use: [b],
})
useSomething({ use: [c] })
相当于:
createQuery({ use: [a, b, c] })
每个中间件包装下一个中间件,最后一个只包装 useQuery hook。例如:
createQuery({ use: [a, b, c] })
中间件执行的顺序是 a → b → c
,如下所示:
enter a
enter b
enter c
useQuery()
exit c
exit b
exit a
在 ReactQuery v5 中,QueryClient
将是 useQuery
和 useMutation
的第二个参数。 如果你在全局中有多个 QueryClient
,你应该在中间件钩子中接收 QueryClient
const useSomething = createQuery({
use: [
function myMiddleware(useQueryNext) {
// 你应该接收 queryClient 作为第二个参数
return (options, queryClient) => {
const client = useQueryClient(queryClient)
// ...
return useQueryNext(options, queryClient)
}
},
],
})
// 如果你传入另一个 QueryClient
useSomething({...}, anotherQueryClient)
您可以使用 inferData
或 inferVariables
提取任何自定义 hook 的 TypeScript 类型
import { inferData, inferFnData, inferError, inferVariables, inferOptions } from 'react-query-kit'
const useProjects = createInfiniteQuery<Response, Variables>(...)
inferData<typeof useProjects> // InfiniteData<Response>
inferFnData<typeof useProjects> // Response
inferVariables<typeof useProjects> // Variables
inferError<typeof useProjects> // Error
inferOptions<typeof useProjects> // InfiniteQueryHookOptions<...>
Looking to contribute? Look for the Good First Issue label.
请针对错误、缺少文档或意外行为提出问题。
请提交问题以建议新功能。 通过添加对功能请求进行投票 一个 👍。 这有助于维护人员优先处理要处理的内容。
MIT