🕊️ 一个用于 ReactQuery 的工具包,它能使 ReactQuery 更易复用和类型安全
queryKeyqueryClient 的操作更清楚地关联到哪个自定义 hookEnglish | 简体中文
$ npm i react-query-kit
# or
$ yarn add react-query-kit
如果您还在使用 React Query Kit v2? 请在此处查看 v2 文档:https://github.com/liaoliao666/react-query-kit/tree/v2#readme.
import { QueryClient, dehydrate } from '@tanstack/react-query'
import { createQuery } from 'react-query-kit'
type Data = { title: string; content: string }
type Variables = { id: number }
const usePost = createQuery({
queryKey: ['posts'],
fetcher: (variables: Variables): Promise<Data> => {
return fetch(`/posts/${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 (
{data?.title}
{data?.content}
)
}
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)) // Data
// setQueryData
queryClient.setQueryData(usePost.getKey(variables), {...})
Options
fetcher: (variables: TVariables, context: QueryFunctionContext<QueryKey, TPageParam>) => TFnData | Promise<TFnData>variables?: TVariablesvariables 将是 fetcher 的第一个参数和 queryKey 数组的最后一个元素use: Middleware[]Expose Methods
fetcher: (variables: TVariables, context: QueryFunctionContext<QueryKey, TPageParam>) => TFnData | Promise<TFnData>getKey: (variables: TVariables) => QueryKeygetOptions: (variables: TVariables) => UseInfiniteQueryOptionsgetFetchOptions: (variables: TVariables) => ({ queryKey, queryFn, queryKeyHashFn })import { QueryClient, dehydrate } from '@tanstack/react-query'
import { createInfiniteQuery } from 'react-query-kit'
type Data = { projects: { id: string; name: string }[]; nextCursor: number }
type Variables = { active: boolean }
const useProjects = createInfiniteQuery({
queryKey: ['projects'],
fetcher: (variables: Variables, { pageParam }): Promise<Data> => {
return fetch(
`/projects?cursor=${pageParam}?active=${variables.active}`
).then(res => res.json())
},
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
initialPageParam: 0,
})
const variables = { active: true }
// example
export default function Page() {
// queryKey equals to ['projects', { active: true }]
const { data, fetchNextPage, hasNextPage, isFetching, isFetchingNextPage } =
useProjects({ variables })
return (
{data.pages.map((group, i) => (
<React.Fragment key={i}>
{group.projects.map(project => (
{project.name}
))}
</React.Fragment>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? 'Loading more...'
: hasNextPage
? 'Load More'
: 'Nothing more to load'}
</button>
{isFetching && !isFetchingNextPage ? 'Fetching...' : null}
)
}
// 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
fetcher: (variables: TVariables, context: QueryFunctionContext<QueryKey, TPageParam>) => TFnData | Promise<TFnData>variables?: TVariablesvariables 将是 fetcher 的第一个参数和 queryKey 数组的最后一个元素use: Middleware[]Expose Methods
fetcher: (variables: TVariables, context: QueryFunctionContext<QueryKey, TPageParam>) => TFnData | Promise<TFnData>getKey: (variables: TVariables) => QueryKeygetOptions: (variables: TVariables) => UseInfiniteQueryOptionsgetFetchOptions: (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',
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 (
{mutation.isPending ? (
'Adding todo...'
) : (
<>
{mutation.isError ? (
An error occurred: {mutation.error.message}
) : null}
{mutation.isSuccess ?
Todo added!
: null}
<button
onClick={() => {
mutation.mutate({ title: 'Do Laundry', content: 'content...' })
}}
>
Create Todo
</button>
</>
)}
)
}
// usage outside of react component
useAddTodo.mutationFn({ title: 'Do Laundry', content: 'content...' })
Options
use: Middleware[]Expose Methods
getKey: () => MutationKeygetOptions: () => UseMutationOptionsmutationFn: ExposeMutationFn<TData, TVariables>router 允许您创建整个 API 的形状
import { router } from 'react-query-kit'
const post = router(`post`, {
byId: router.query({
fetcher: (variables: { id: number }) =>
fetch(`/posts/${variables.id}`).then(res => res.json()),
use: [myMiddleware],
}),
list: router.infiniteQuery({
fetcher: (_variables, { pageParam }) =>
fetch(`/posts/?cursor=${pageParam}`).then(res => res.json()),
getNextPageParam: lastPage => lastPage.nextCursor,
initialPageParam: 0,
}),
add: router.mutation({
mutationFn: async (variables: { title: string; content: string }) =>
fetch('/posts', {
method: 'POST',
body: JSON.stringify(variables),
}).then(res => res.json()),
}),
// nest router
command: {
report: router.mutation({ mutationFn }),
promote: router.mutation({ mutationFn }),
},
})
// get root key
post.getKey() // ['post']
// hooks
post.byId.useQuery({ variables: { id: 1 } })
post.byId.useSuspenseQuery({ variables: { id: 1 } })
post.list.useInfiniteQuery()
post.list.useSuspenseInfiniteQuery()
post.add.useMutation()
post.command.report.useMutation()
// expose methods
post.byId.getKey({ id: 1 }) // ['post', 'byId', { id: 1 }]
post.byId.getFetchOptions({ id: 1 })
post.byId.getOptions({ id: 1 })
post.byId.fetcher({ id: 1 })
post.add.getKey() // ['post', 'add']
post.add.getOptions()
post.add.mutationFn({ title: 'title', content: 'content' })
// infer types
type Data = inferData<typeof post.list>
type FnData = inferFnData<typeof post.list>
type Variables = inferVariables<typeof post.list>
type Error = inferError<typeof post.list>
import { router } from 'react-query-kit'
const user = router(`user`, {})
const post = router(`post`, {})
const k = {
user,
post,
}
type Router = (key: string | unknown[], config: TConfig) => TRouter
Expose Methods
query
与 createQuery 类似,但无需选项 queryKeyinfiniteQuery
与 createInfiniteQuery 类似,但无需选项 queryKeymutation
与 createMutation 类似,但无需选项 mutationKey此功能的灵感来自于 SWR 的中间件功能。
中间件接收 hook,可以在运行它之前和之后执行逻辑。如果有多个中间件,则每个中间件包装下一个中间件。列表中的最后一个中间件将接收原始的 hook。
import { QueryClient } from '@tanstack/react-query'
import { Middleware, MutationHook, QueryHook, getKey } from 'react-query-kit'
const logger: Middleware<QueryHook<Data, Variables>> = useQueryNext => {
return options => {
const log = useLogger()
const fetcher = (variables, context) => {
log(context.queryKey, variables)
return options.fetcher(variables, context)
}
return useQueryNext({
...options,
fetcher,
})
}
}
const useUser = createQuery<Data, Variables>({
use: [logger],
})
// 全局中间件
const queryMiddleware: Middleware<QueryHook> = useQueryNext => {
return options => {
// 你还可以通过函数 getKey 获取 queryKey
const fullKey = getKey(options.queryKey, 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)
$ claude mcp add react-query-kit \
-- python -m otcore.mcp_server <graph>