跳转到内容

Query

TanStack Query 提供了一套管理异步状态(通常是外部数据)的函数。

引用 Overview 文档

React Query 常被描述为 React 缺失的数据获取库,但从更技术的角度来说,它让你在 React 应用中获取、缓存、同步和更新服务端状态变得轻而易举。

jotai-tanstack-query 是 Jotai 的 TanStack Query 扩展库。它提供了涵盖所有 TanStack Query 功能的出色接口,让你能将这些功能与现有的 Jotai 状态结合使用。

jotai-tanstack-query 目前支持 TanStack Query v5。

除了 jotai 之外,你还需要安装 jotai-tanstack-query@tanstack/query-core 才能使用此扩展。

Terminal window
npm install jotai-tanstack-query @tanstack/query-core

你可以在应用中渐进式采用 jotai-tanstack-query。它不是非此即彼的方案,你只需确保使用相同的 QueryClient 实例即可。QueryClient 设置

// existing useQueryHook
const { data, isPending, isError } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
})
// jotai-tanstack-query
const todosAtom = atomWithQuery(() => ({
queryKey: ['todos'],
}))
const [{ data, isPending, isError }] = useAtom(todosAtom)

所有函数遵循相同的签名。

const dataAtom = atomWithSomething(getOptions, getQueryClient)

第一个 getOptions 参数是一个返回 observer 输入的函数。 第二个可选的 getQueryClient 参数是一个返回 QueryClient 的函数。

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>Loading...</div>
if (isError) return <div>Error</div>
return <div>{JSON.stringify(data)}</div>
}

atomWithInfiniteQueryatomWithQuery 非常相似,但它用于 InfiniteQuery,适用于需要分页的数据。你可以在这里阅读更多关于无限查询的内容

渲染可以不断”加载更多”数据到现有数据集或”无限滚动”的列表,也是一种非常常见的 UI 模式。React Query 支持一个有用的 useQuery 变体 useInfiniteQuery 来查询这类列表。

与标准查询原子的显著区别在于额外的选项 getNextPageParamgetPreviousPageParam,你将使用它们来告知查询如何获取额外的页面。

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>Loading...</div>
if (isError) return <div>Error</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()}>Next</button>
</>
)
}

atomWithMutation 创建一个新原子,实现了 TanStack Query 的标准 Mutation

与查询不同,mutation 通常用于创建/更新/删除数据或执行服务端副作用。

const 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' })}>Click me</button>
<pre>{JSON.stringify(status, null, 2)}</pre>
</div>
)
}

atomWithMutationState 创建一个新原子,使你可以访问 MutationCache 中的所有 mutation。

const mutationStateAtom = atomWithMutationState((get) => ({
filters: {
mutationKey: ['posts'],
},
}))

jotai-tanstack-query 也可以与 React 的 Suspense 一起使用。

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>
}
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()}>Next</button>
</>
)
}

在项目中引用同一个 Query Client 实例

Section titled “在项目中引用同一个 Query Client 实例”

也许你的项目中有一些自定义 hook 使用 useQueryClient() hook 获取 QueryClient 对象并调用其方法。

为确保引用同一个 QueryClient 对象,请在项目根部包裹 <Provider> 并使用与传递给 QueryClientProvider 相同的 queryClient 值初始化 queryClientAtom

如果没有这一步,useQueryAtom 将引用与使用 useQueryClient() hook 获取 queryClient 的 hook 不同的 QueryClient

另外,你也可以通过 getQueryClient 参数指定你的 queryClient

在下面的示例中,我们有一个 mutation hook useTodoMutation 和一个查询 todosAtom

我们在根节点 <App> 中包含了初始化步骤。

虽然它们引用相同的查询键('todos'),但如果没有完成 Provider 初始化步骤useTodoMutationonSuccess 的失效操作将不会触发。

这将导致 todosAtom 显示过期数据,因为它没有被提示重新获取。

注意:使用 Typescript 时,建议在将 queryClient 值传递给 useHydrateAtoms 时使用 Map。你可以在渲染时初始化状态文档中找到可用的示例。

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>
{/*
This Provider initialisation step is needed so that we reference the same
queryClient in both atomWithQuery and other parts of the app. Without this,
our useQueryClient() hook will return a different QueryClient object
*/}
<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,
}
)
}

所有原子都可以在服务端渲染应用的上下文中使用,例如 Next.js 或 Gatsby 应用。你可以使用 React Query 为 SSR 应用支持的两种方式水合initialData

获取错误将被抛出,可以通过 ErrorBoundary 捕获。 重新获取可能可以从临时错误中恢复。

查看可运行的示例了解更多。

要使用开发者工具,你需要额外安装。

Terminal window
npm install @tanstack/react-query-devtools

你只需在 <QueryClientProvider /> 内放置 <ReactQueryDevtools />

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>
)
}

所有原子签名已更改,以与 TanStack Query 更加一致。 v0.8.0 只返回单个原子,而不是原子元组,因此名称从 atomsWithSomething 改为 atomWithSomething

const [dataAtom, statusAtom] = atomsWithSomething(getOptions, getQueryClient)
const dataAtom = atomWithSomething(getOptions, getQueryClient)

在之前版本的 jotai-tanstack-query 中,查询原子 atomsWithQueryatomsWithInfiniteQuery 返回一个原子元组:[dataAtom, statusAtom]。这种设计将数据和状态分成了两个不同的原子。

  • dataAtom 用于访问实际数据(TData)。
  • statusAtom 提供状态对象(QueryObserverResult<TData, TError>),包括 isPendingisError 等附加属性。

在 v0.8.0 中,它们被替换为 atomWithQueryatomWithInfiniteQuery,只返回单个 dataAtom。这个 dataAtom 现在直接提供 QueryObserverResult<TData, TError>,与 TanStack Query 绑定的行为紧密对齐。

要迁移到新版本,请将单独的 dataAtomstatusAtom 用法替换为统一的 dataAtom,它现在同时包含数据和状态信息。

const [dataAtom, statusAtom] = atomsWithQuery(/* ... */);
const [data] = useAtom(dataAtom);
const [status] = useAtom(statusAtom);
const dataAtom = atomWithQuery(/* ... */);
const [{ data, isPending, isError }] = useAtom(dataAtom);

atomsWithQueryatomsWithInfiniteQuery 类似,atomWithMutation 也返回单个原子而不是原子元组。原子值的返回类型为 MutationObserverResult<TData, TError, TVariables, TContext>

const [, postAtom] = atomsWithMutation(/* ... */);
const [post, mutate] = useAtom(postAtom); // Accessing mutation status from post; and mutate() to execute the mutation
const postAtom = atomWithMutation(/* ... */);
const [{ data, error, mutate }] = useAtom(postAtom); // Accessing mutation result and mutate method from the same atom

Open in StackBlitz

Open in StackBlitz

Open in StackBlitz