TanStack Query
TanStack Query 提供了一组用于管理异步状态(通常是外部数据)的函数。
来自概述文档:
React Query 通常被描述为 React 缺少的数据获取库,但在更专业的术语中,它使 React 应用程序中的 获取、缓存、同步和更新服务器状态 变得轻而易举。
jotai-tanstack-query 是一个用于 TanStack Query 的 Jotai 集成库。 它为所有 TanStack 查询功能提供了一个美妙的界面,使您能够将这些功能与现有的 Jotai 状态结合使用。
安装
除了 jotai
之外,您还必须安装 jotai-tanstack-query
和 @tanstack/query-core
才能使用集成。
yarn add jotai-tanstack-query @tanstack/query-core
导出函数
- QueryObserver 的
atomsWithQuery
- InfiniteQueryObserver 的
atomsWithInfiniteQuery
- MutationObserver 的
atomsWithMutation
所有三个函数都遵循相同的签名。
const [dataAtom, statusAtom] = atomsWithSomething(getOptions, getQueryClient);
第一个 getOptions
参数是一个将输入返回给观察者的函数。
第二个可选的 getQueryClient
参数是一个返回 QueryClient 的函数。
返回值有两个原子。
第一个称为 dataAtom
,它是来自观察者的数据的原子。 dataAtom
需要 Suspense。
第二个称为 statusAtom
,它是来自观察者的完整结果的原子。 statusAtom
不需要 Suspense。
来自观察者的数据也包含在 statusAtom
中,因此如果您不使用 Suspense,则不需要使用 dataAtom。
atomsWithQuery
使用
atomsWithQuery
从 TanStack Query 创建实现标准 Query
的新原子。
查询是对绑定到唯一键的异步数据源的声明性依赖。 查询可以与任何基于 Promise 的方法(包括 GET 和 POST 方法)一起使用以从服务器获取数据。
import { atom, useAtom } from "jotai";import { atomsWithQuery } from "jotai-tanstack-query";const idAtom = atom(1);const [userAtom] = atomsWithQuery((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>;};
atomsWithInfiniteQuery
使用
atomsWithInfiniteQuery
与 atomsWithQuery
非常相似,但它适用于 InfiniteQuery
,用于要分页的数据。 您可以在此处阅读有关无限查询的更多信息。
可以将 更多数据 加载到现有数据集或 无限滚动 中的渲染列表也是一种非常常见的 UI 模式。 React Query 支持一个有用的 useQuery 版本,称为 useInfiniteQuery 用于查询这些类型的列表。
标准查询原子之间的 一个显着区别是附加选项 getNextPageParam
和 getPreviousPageParam
,您将使用它们来指示查询如何获取任何其他页面。
import { atom, useAtom } from "jotai";import { atomsWithInfiniteQuery } from "jotai-tanstack-query";const idAtom = atom(1);const [userAtom] = atomsWithInfiniteQuery((get) => ({queryKey: ["users", get(idAtom)],queryFn: async ({ queryKey: [, id] }) => {const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);return res.json();},// 无限查询可以支持分页 fetchgetNextPageParam: (lastPage, pages) => lastPage.nextCursor,}));const UserData = () => {const [data] = useAtom(userAtom);return data.pages.map((userData, index) => (<div key={index}>{JSON.stringify(userData)}</div>));};
获取页面并重新 fetch
使用与上述示例中相同的原子,我们可以将操作分派给 userAtom
。
const UserData = () => {const [data, dispatch] = useAtom(userAtom);const handleFetchNextPage = () => dispatch({ type: "fetchNextPage" });const handleFetchPreviousPage = () => dispatch({ type: "fetchPreviousPage" });};
atomsWithMutation
使用
atomsWithMutation
从 TanStack Query 中创建实现标准 Mutation
的新原子。
与查询不同,mutations 通常用于 create/update/delete 数据或执行服务器副作用。
import { atom, useAtom } from "jotai";import { atomsWithMutation } from "jotai-tanstack-query";const idAtom = atom(1);const [, postAtom] = atomsWithMutation((get) => ({mutationKey: ["posts"],mutationFn: async ({ title, body }) => {const res = await fetch(`https://jsonplaceholder.typicode.com/posts`, {method: "POST",body: JSON.stringify({ title, body, userId: get(idAtom) }),headers: { "Content-type": "application/json; charset=UTF-8" },});const data = await res.json();return data;},}));const PostData = () => {const [post, mutate] = useAtom(postAtom);return (<div><button onClick={() => mutate([{ title: "foo", body: "bar" }])}>Click me</button><div>{JSON.stringify(post)}</div></div>);};
在您的项目中引用同一个 Query Client 实例
也许您的项目中有一些自定义 hook,这些 hook 利用 useQueryClient()
hook 来获取 QueryClient
对象并调用其方法。
为确保您引用相同的 QueryClient
对象,请务必将项目的根包装在 <Provider>
中,并使用您提供给 QueryClientProvider
的相同 queryClient
值初始化 queryClientAtom
。
如果没有这一步,useQueryAtom
将从任何使用 useQueryClient()
hook 获取 queryClient
的 hook 中引用一个单独的 QueryClient
。
或者,您可以使用 getQueryClient
参数指定 queryClient
。
示例
在下面的示例中,我们有一个 mutation hook useTodoMutation
和一个查询 todosAtom
。
我们在我们的根 <App>
节点中包含了一个初始化步骤。
尽管它们引用相同的查询 key('todos'
)方法,但如果未完成 Provider
初始化步骤,则不会触发 useTodoMutation
中的 onSuccess
失效。
这将导致 todosAtom
显示旧数据,因为它没有被提示重新获取。
import {useMutation,useQueryClient,QueryClient,QueryClientProvider,} from "@tanstack/react-query";import { atomsWithQuery, queryClientAtom } from "jotai-tanstack-query";const queryClient = new QueryClient();export const App = () => {return (<QueryClientProvider client={queryClient}>{/* This Provider initialisation step is needed so that we reference the samequeryClient in both atomWithQuery and other parts of the app. Without this,our useQueryClient() hook will return a different QueryClient object */}<Provider initialValues={[[queryClientAtom, queryClient]]}><App /></Provider></QueryClientProvider>);};export const [todosAtom] = atomsWithQuery((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 支持在 SSR 应用程序中使用,hydration 或 initialData
。
错误处理
使用 dataAtom
,将抛出获取错误,可以使用 ErrorBoundary 捕获 。
重新获取可能会从临时错误中恢复。
请参阅工作示例 了解更多信息。
Devtools
为了使用 Devtools,您需要另外安装它。
$ npm i @tanstack/react-query-devtools# or$ pnpm add @tanstack/react-query-devtools# or$ yarn add @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,},},});export const App = () => {return (<QueryClientProvider client={queryClient}><Provider initialValues={[[queryClientAtom, queryClient]] as const}><App /></Provider><ReactQueryDevtools /></QueryClientProvider>);};