JotaiJotai

状態
原始和灵活的 React 状态管理

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

导出函数

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

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 使用

atomsWithInfiniteQueryatomsWithQuery 非常相似,但它适用于 InfiniteQuery,用于要分页的数据。 您可以在此处阅读有关无限查询的更多信息

可以将 更多数据 加载到现有数据集或 无限滚动 中的渲染列表也是一种非常常见的 UI 模式。 React Query 支持一个有用的 useQuery 版本,称为 useInfiniteQuery 用于查询这些类型的列表。

标准查询原子之间的一个显着区别是附加选项 getNextPageParamgetPreviousPageParam,您将使用它们来指示查询如何获取任何其他页面。

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();
},
// 无限查询可以支持分页 fetch
getNextPageParam: (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 same
queryClient 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 应用程序中使用,hydrationinitialData

错误处理

使用 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>
);
};

示例

基础演示

Devtools 演示

黑客新闻