这是使用React Query与TypeScript的系列文章中的另一篇文章。
以前的帖子:
这篇文章将介绍用React Query从Web服务请求中设置应用级状态的两种方法。应用程序级别的状态被存储在React上下文中。
查询客户端提供者
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。 - 我们从查询中删除了去结构化的
status和error状态变量。取而代之的是,我们对data状态变量进行了结构化,并将其别名为user。 - 我们删除了查询中的
onSuccess函数。
Header 组件中的消耗代码保持不变。
很好😊
这第二个例子中的代码可以在CodeSandbox中找到:codesandbox.io/s/react-que…