用React查询设置应用程序状态的指南

95 阅读4分钟

这是使用React Query与TypeScript的系列文章中的另一篇文章。

以前的帖子:

这篇文章将介绍用React Query从Web服务请求中设置应用级状态的两种方法。应用程序级别的状态被存储在React上下文中。

App state 查询客户端提供者

React Query需要在使用它的组件上面有一个QueryClientProvider

import { QueryClient, QueryClientProvider } from "react-query";
...

const queryClient = new QueryClient();
render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>,
  rootElement
);

我们在组件树的顶部添加了QueryClientProvider ,使React Query对任何组件都可用。

应用程序数据上下文

我们的应用程序级别的状态将在一个React上下文中,这里是它的类型和创建:

// AppDataProvider.tsx
export type User = {
  name: string;
};
type AppDataType = {
  user: User | undefined;
  setUser: (user: User) => void;
};
const AppData = React.createContext<AppDataType>(undefined!);

上下文在一个user 属性中包含了关于用户的信息。在这个例子中,我们只存储了用户的名字。上下文也有一个函数,setUser ,消费者可以用它来设置用户对象。

我们把undefined 传递到createContext ,并在它后面使用一个非空的断言(!)。这样,我们就不必在与上下文交互的消费代码中不必要地检查undefined

这里是这个上下文的一个提供者组件:

// AppDataProvider.tsx
export function AppDataProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = React.useState<User | undefined>(undefined);
  return (
    <AppData.Provider value={{ user, setUser }}>{children}</AppData.Provider>
  );
}

这个组件包含了user 状态,并将其提供给该上下文的消费者。

这里有一个钩子,消费者可以用它来访问该上下文:

// AppDataProvider.tsx
export const useAppData = () => React.useContext(AppData);

我们现在可以使用组件树上的提供者组件了:

import { AppDataProvider } from "./AppDataContext";...
render(
  <QueryClientProvider client={queryClient}>
    <AppDataProvider>      <App />
    </AppDataProvider>  </QueryClientProvider>,
  rootElement
);

取值函数

我们的React Query的获取函数接收一个查询键。在这个例子中,这是一个包含资源名称和参数的元组,用于获取用户记录:

type Params = {
  queryKey: [string, { id: number }];
};
async function getUser(params: Params) {
  const [, { id }] = params.queryKey;
  const response = await fetch(`https://swapi.dev/api/people/${id}/`);
  if (!response.ok) {
    throw new Error("Problem fetching user");
  }
  const user = await response.json();
  assertIsUser(user);

  return user;
}

我们假设《星球大战》的API包含我们的用户。

fetch 是用来提出请求的。如果响应不成功,就会抛出一个错误。React Query将为我们管理错误,将 状态设置为 ,将错误设置为 对象的抛出。status "error" Error

获取函数预计将返回我们想在组件中使用的响应数据。我们使用一个叫做assertIsUser 的类型断定函数来确保数据的类型正确:

import { User } from "./AppDataContext";
...
function assertIsUser(user: any): asserts user is User {
  if (!("name" in user)) {
    throw new Error("Not user");
  }
}

查询

我们将在组件中使用React Query来获取用户数据并将其设置在应用状态中:

export function App() {
  const { status, error } = useQuery<User, Error>(
    ["user", { id: 1 }],
    getUser
  );

  if (status === "loading") {
    return <div>...</div>;
  }
  if (status === "error") {
    return <div>{error!.message}</div>;
  }

  return <Header />;
}

我们使用React Query的useQuery 钩子来执行getUser 的获取函数,并传入一个用户ID1 让它获取。

我们使用status 状态变量,在不同的获取状态下渲染各种元素。Header 组件在数据被获取后被渲染。我们稍后会看一下Header 组件。

设置查询中的应用数据

我们需要设置在应用程序数据上下文中获取的用户数据。让我们先用useAppData 钩子获得对setter函数的访问:

import { useAppData, User } from "./AppDataContext";...
export function App() {
  const { setUser } = useAppData();  ...
}

有一个onSuccess 函数,可以在React Query成功执行获取数据的函数后执行。我们可以用它来更新应用程序的数据上下文:

export function App() {
  const { setUser } = useAppData();

  const { status, error } = useQuery<User, Error>(
    ["user", { id: 1 }],
    getUser,
    { onSuccess: (data) => setUser(data) }  );
}

onSuccess 函数接收了已经获取的数据,所以我们将其传递给setUser 函数。

渲染带有应用程序数据的页眉

低级别的组件现在可以访问应用数据上下文。这里是显示用户名的Header 组件:

import { useAppData } from "./AppDataContext";

export function Header() {
  const { user } = useAppData();
  return user ? <header>{user.name}</header> : null;
}

这个例子的代码可以在CodeSandbox中找到:codesandbox.io/s/react-que…

将查询移至AppDataProvider

这很好,显示了我们如何从React Query推送状态到其他状态提供者。然而,我们可以通过使用React Query中的状态并删除我们自己的状态来简化这个例子。

让我们先把React Query移到AppDataProvider 内:

// AppDataContext.tsx
...
import { useQuery } from "react-query";
...
type AppDataType = {
  user: User | undefined;
};
...
export function AppDataProvider({ children }: { children: React.ReactNode }) {
  const { data: user } = useQuery<User, Error>(["user", { id: 1 }], getUser);

  return <AppData.Provider value={{ user }}>{children}</AppData.Provider>;
}
...

我们最终会删除相当多的代码:

  • 我们已经从上下文中删除了setUser 函数。
  • 我们从上下文提供者中删除了useState
  • 我们从查询中删除了去结构化的statuserror 状态变量。取而代之的是,我们对data 状态变量进行了结构化,并将其别名为user
  • 我们删除了查询中的onSuccess 函数。

Header 组件中的消耗代码保持不变。

很好😊

这第二个例子中的代码可以在CodeSandbox中找到:codesandbox.io/s/react-que…