Query
TanStack Query 提供了一组用于管理异步状态(通常是外部数据)的函数。
从概述文档中:
React Query 常被描述为 React 缺失的数据获取库,但更技术的说法是,它使得在你的 React 应用中获取、缓存、同步和更新服务器状态变得轻而易举。
jotai-tanstack-query 是 TanStack Query 的 Jotai 扩展库。它提供了一个与所有 TanStack Query 功能的美妙接口,使你能够将这些功能与你现有的 Jotai 状态结合使用。
支持
jotai-tanstack-query 目前支持 TanStack Query v5。
安装
除了 jotai
,你还需要安装 jotai-tanstack-query
、@tanstack/query-core
和 wonka
来使用这个扩展。
npm i jotai-tanstack-query @tanstack/query-core wonka
渐进式采用
你可以在你的应用中渐进式地采用 jotai-tanstack-query
。它不是全有或全无的解决方案。你只需要确保你正在使用相同的 QueryClient 实例。QueryClient 设置。
// 现有的 useQueryHookconst { data, isPending, isError } = useQuery({queryKey: ['todos'],queryFn: fetchTodoList});// jotai-tanstack-queryconst todosAtom = atomWithQuery(() => ({queryKey: ['todos'],}))const [{ data, isPending, isError }] = useAtom(todosAtom)
导出的函数
atomWithQuery
对应 useQueryatomWithInfiniteQuery
对应 useInfiniteQueryatomWithMutation
对应 useMutationatomWithSuspenseQuery
对应 useSuspenseQueryatomWithSuspenseInfiniteQuery
对应 useSuspenseInfiniteQueryatomWithMutationState
对应 useMutationState
所有函数遵循相同的签名。
const dataAtom = atomWithSomething(getOptions, getQueryClient)
第一个 getOptions
参数是一个返回观察者输入的函数。
第二个可选的 getQueryClient
参数是一个返回 QueryClient 的函数。
atomWithQuery 的使用
atomWithQuery
创建了一个新的原子,它实现了来自 TanStack Query 的标准 Query
。
import { atom, useAtom } from 'jotai'import { atomWithQuery } from 'jotai-tanstack-query'const idAtom = atom(1)const userAtom = atomWithQuery((get) => ({queryKey: ['users', get(idAtom)],queryFn: async ({ queryKey: [, id] }) => {const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)return res.json()},}))const UserData = () => {const [{ data, isPending, isError }] = useAtom(userAtom)if (isPending) return <div>加载中...</div>if (isError) return <div>错误</div>return <div>{JSON.stringify(data)}</div>}
atomWithInfiniteQuery 的使 用
atomWithInfiniteQuery
与 atomWithQuery
非常相似,但它是用于 InfiniteQuery
,这是用于分页数据的。你可以在这里阅读更多关于无限查询的内容。
渲染可以增量 "加载更多" 数据到现有数据集或 "无限滚动" 的列表也是一个非常常见的 UI 模式。React Query 支持一个有用的 useQuery 版本,叫做 useInfiniteQuery,用于查询这种类型的列表。
一个标准查询原子的显著区别是额外的选项 getNextPageParam
和 getPreviousPageParam
,这是你将用来指导查询如何获取任何额外的页面。
import { atom, useAtom } from 'jotai'import { atomWithInfiniteQuery } from 'jotai-tanstack-query'const postsAtom = atomWithInfiniteQuery(() => ({queryKey: ['posts'],queryFn: async ({ pageParam }) => {const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam}`)return res.json()},getNextPageParam: (lastPage, allPages, lastPageParam) => lastPageParam + 1,initialPageParam: 1,}))const Posts = () => {const [{ data, fetchNextPage, isPending, isError, isFetching }] =useAtom(postsAtom)if (isPending) return <div>加载中...</div>if (isError) return <div>错误</div>return (<>{data.pages.map((page, index) => (<div key={index}>{page.map((post: any) => (<div key={post.id}>{post.title}</div>))}</div>))}<button onClick={() => fetchNextPage()}>下一页</button></>)}### atomWithMutation 的使用 {#atomwithmutation-usage}`atomWithMutation` 创建了一个新的原子,它实现了来自 TanStack Query 的标准 [`Mutation`](https://tanstack.com/query/v5/docs/guides/mutations)。> 与查询不同,mutations 通常用于创建/更新/删除数据或执行服务器端效果。```tsxconst postAtom = atomWithMutation(() => ({mutationKey: ['posts'],mutationFn: async ({ title }: { title: string }) => {const res = await fetch(`https://jsonplaceholder.typicode.com/posts`, {method: 'POST',body: JSON.stringify({title,body: 'body',userId: 1,}),headers: {'Content-type': 'application/json; charset=UTF-8',},})const data = await res.json()return data},}))const Posts = () => {const [{ mutate, status }] = useAtom(postAtom)return (<div><button onClick={() => mutate({ title: 'foo' })}>点击我</button><pre>{JSON.stringify(status, null, 2)}</pre></div>)}
atomWithMutationState 的使用
atomWithMutationState
创建了一个新的原子,它可以让你访问 MutationCache
中的所有 mutations。
const mutationStateAtom = atomWithMutationState((get) => ({filters: {mutationKey: ['posts'],},}))
Suspense
jotai-tanstack-query 也可以与 React 的 Suspense 一起使用。
atomWithSuspenseQuery 的使用
import { atom, useAtom } from 'jotai'import { atomWithSuspenseQuery } from 'jotai-tanstack-query'const idAtom = atom(1)const userAtom = atomWithSuspenseQuery((get) => ({queryKey: ['users', get(idAtom)],queryFn: async ({ queryKey: [, id] }) => {const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)return res.json()},}))const UserData = () => {const [{ data }] = useAtom(userAtom)return <div>{JSON.stringify(data)}</div>}
atomWithSuspenseInfiniteQuery 的使用
import { atom, useAtom } from 'jotai'import { atomWithSuspenseInfiniteQuery } from 'jotai-tanstack-query'const postsAtom = atomWithSuspenseInfiniteQuery(() => ({queryKey: ['posts'],queryFn: async ({ pageParam }) => {const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam}`)return res.json()},getNextPageParam: (lastPage, allPages, lastPageParam) => lastPageParam + 1,initialPageParam: 1,}))const Posts = () => {const [{ data, fetchNextPage, isPending, isError, isFetching }] =useAtom(postsAtom)return (<>{data.pages.map((page, index) => (<div key={index}>{page.map((post: any) => (<div key={post.id}>{post.title}</div>))}</div>))}<button onClick={() => fetchNextPage()}>下一页</button></>)}
在你的项目中引用相同的 Query Client 实例
也许你的项目中有一些自定义的钩子,它们使用 useQueryClient()
钩子来获取 QueryClient
对象并调用其方法。
为了确保你引用的是相同的 QueryClient
对象,请确保在你的项目的根部包裹一个 <Provider>
,并用你提供给 QueryClientProvider
的相同的 queryClient
值初始 化 queryClientAtom
。
如果没有这一步,useQueryAtom
将引用与任何使用 useQueryClient()
钩子获取 queryClient 的钩子不同的 QueryClient
。
或者,你可以用 getQueryClient
参数指定你的 queryClient
。
示例
在下面的示例中,我们有一个 mutation 钩子 useTodoMutation
和一个查询 todosAtom
。
我们在根 <App>
节点中包含了一个初始化步骤。
尽管它们引用了相同的查询键('todos'
),但如果没有进行 Provider
初始化步骤,useTodoMutation
中的 onSuccess
无效化将不会触发。
这将导致 todosAtom
显示过时的数据,因为它没有被提示重新获取。
import { Provider } from 'jotai/react'import { useHydrateAtoms } from 'jotai/react/utils'import {useMutation,useQueryClient,QueryClient,QueryClientProvider,} from '@tanstack/react-query'import { atomWithQuery, queryClientAtom } from 'jotai-tanstack-query'const queryClient = new QueryClient()const HydrateAtoms = ({ children }) => {useHydrateAtoms([[queryClientAtom, queryClient]])return children}export const App = () => {return (<QueryClientProvider client={queryClient}><Provider>{/*这个 Provider 初始化步骤是必需的,以便我们在 atomWithQuery 和应用程序的其他部分都引用相同的queryClient。如果没有这个步骤,我们的 useQueryClient() 钩子将返回一个不同的 QueryClient 对象*/}<HydrateAtoms><App /></HydrateAtoms></Provider></QueryClientProvider>)}export const todosAtom = atomWithQuery((get) => {return {queryKey: ['todos'],queryFn: () => fetch('/todos'),}})export const useTodoMutation = () => {const queryClient = useQueryClient()return useMutation(async (body: todo) => {await fetch('/todo', { Method: 'POST', Body: body })},{onSuccess: () => {void queryClient.invalidateQueries(['todos'])},onError,})}
服务器端渲染(SSR)支持
所有的原子都可以在服务器端渲染的应用中使用,例如 next.js 应用或 Gatsby 应用。你可以使用 React Query 支持的两种选项,hydration 或 initialData
。
错误处理
获取错误将会被抛出,并且可以被 ErrorBoundary 捕获。 重新获取可能会从临时错误中恢复。
查看工作示例以了解更多。
开发者工具
为了使用开发者工具,你需要额外安装它。
npm i @tanstack/react-query-devtools
你需要做的就是将 <ReactQueryDevtools />
放在 <QueryClientProvider />
内。
import {QueryClientProvider,QueryClient,QueryCache,} from '@tanstack/react-query'import { ReactQueryDevtools } from '@tanstack/react-query-devtools'import { queryClientAtom } from 'jotai-tanstack-query'const queryClient = new QueryClient({defaultOptions: {queries: {staleTime: Infinity,},},})const HydrateAtoms = ({ children }) => {useHydrateAtoms([[queryClientAtom, queryClient]])return children}export const App = () => {return (<QueryClientProvider client={queryClient}><Provider><HydrateAtoms><App /></HydrateAtoms></Provider><ReactQueryDevtools /></QueryClientProvider>)}
迁移到 v0.8.0
原子签名的变化
所有的原子签名都已经改变,以与 TanStack Query 更加一致。
v0.8.0 只返回一个原子,而不是一对原子,因此从 atomsWithSomething
改名为 atomWithSomething
。
- const [dataAtom, statusAtom] = atomsWithSomething(getOptions, getQueryClient)+ const dataAtom = atomWithSomething(getOptions, getQueryClient)
简化的返回结构
在 jotai-tanstack-query
的前一个版本中,查询原子 atomsWithQuery
和 atomsWithInfiniteQuery
返回了一对原子:[dataAtom, statusAtom]
。这种设计将数据及其状态分成了两个不同的原子。
atomWithQuery 和 atomWithInfiniteQuery
dataAtom
被用来访问实际的数据 (TData
)。statusAtom
提供了状态对象 (QueryObserverResult<TData, TError>
),其中包括了额外的属性,如isPending
,isError
等。
在 v0.8.0 中,它们被 atomWithQuery
和 atomWithInfiniteQuery
替代,只返回一个 dataAtom
。这个 dataAtom
现在直接提供了 QueryObserverResult<TData, TError>
,使其与 Tanstack Query 的绑定行为更加接近。
要迁移到新版本,将分开的 dataAtom
和 statusAtom
的使用替换为现在包含数据和状态信息的统一的 dataAtom
。
- const [dataAtom, statusAtom] = atomsWithQuery(/* ... */);- const [data] = useAtom(dataAtom);- const [status] = useAtom(statusAtom);+ const dataAtom = atomWithQuery(/* ... */);+ const [{ data, isPending, isError }] = useAtom(dataAtom);
atomWithMutation
与 atomsWithQuery
和 atomsWithInfiniteQuery
类似,atomWithMutation
也返回一个原子而不是一对原子。原子值的返回类型是 MutationObserverResult<TData, TError, TVariables, TContext>
。
- const [, postAtom] = atomsWithMutation(/* ... */);- const [post, mutate] = useAtom(postAtom); // 从 post 访问 mutation 状态;并使用 mutate() 执行 mutation+ const postAtom = atomWithMutation(/* ... */);+ const [{ data, error, mutate }] = useAtom(postAtom); // 从同一个原子访问 mutation 结果和 mutate 方法