React查询中的依赖请求(详细指南)

207 阅读3分钟

这是关于React Querywith TypeScript的系列文章中的第二篇。在上一篇文章中,我们使用useQuery 钩子做了一个基本的网络服务请求。这篇文章将扩展这个例子,做第二个请求,需要第一个请求中的数据:

Dependent Requests in React Query 我们的需求

目前,我们的React组件请求星球大战API中的people 资源并显示角色的名字。

这就是此刻的组件:

export default function App() {
  const { status, error, data } = useQuery<Character, Error>(
    ["character", { id: 1 }],
    getCharacter
  );

  if (status === "loading") {
    return <div>...</div>;
  }
  if (status === "error") {
    return <div>{error!.message}</div>;
  }
  return data ? <h3>{data.name}</h3> : null;
}

响应中还有一个homeworld 字段,它是一个URL,可以获得角色的母星信息:

{
  "name": "Luke Skywalker",
  ...
  "homeworld": "https://swapi.dev/api/planets/1/",  ...
}

我们需要向这个URL发出请求,并在角色的名字旁边显示其母星的名字。

第二个获取函数

我们将开始创建获取母星的取值函数:

async function getPlanet({
  queryKey,
}: {
  queryKey: [string, { url: string }];
}) {
  const [, { url }] = queryKey;
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error("Problem fetching planet");
  }
  const data = await response.json();
  assertIsPlanet(data);
  return data;
}

查询键被传递到该函数中,它是一个元组,其中第二个元素包含应被用来进行请求的URL。

该函数发出一个简单的请求,如果不成功就会引发一个错误。

数据的类型用assertIsPlanet type assert函数断定为Planet

type Planet = {
  name: string;
};
function assertIsPlanet(data: any): asserts data is Planet {
  if (!("name" in data)) {
    throw new Error("Not home planet");
  }
}

第二个查询

现在让我们使用useQuery 钩子在组件中添加第二个查询:

import { useQuery } from 'react-query'

export function App() {
  const {
    status: characterStatus,
    error: characterError,
    data: characterData,
  } = useQuery<Character, Error>(["character", { id: 1 }], getCharacter);

  const {    status: planetStatus,    error: planetError,    data: planetData,  } = useQuery<Planet, Error>(["planet", { url: characterData?.homeworld }], getPlanet);  ...
}

我们对状态变量进行了别名,这样它们就不会发生冲突。

我们还将字符响应数据中的homeworld 字段传递给了getPlanet 取值函数。

现在让我们进行渲染:

export default function App() {
  ...
  if (characterStatus === "loading" || planetStatus === "loading") {
    return <div>...</div>;
  }
  if (characterStatus === "error") {
    return <div>{characterError!.message}</div>;
  }
  if (planetStatus === "error") {
    return <div>{planetError!.message}</div>;
  }
  return (
    <div>
      {characterData && <h3>{characterData.name}</h3>}
      {planetData && <p>{planetData.name}</p>}
    </div>
  );
}

该组件编译正常,这很好,正确的字符和星球名称会被渲染出来:

Render

然而,如果我们在DevTools的网络面板上看一下,我们会发现在收到字符响应之前,已经发出了一个行星请求。因此,向一个undefined URL发出了请求:

Undefined request 当收到一个字符响应时,发生了重新渲染,第二次调用getPlanet ,并提出了一个有效的请求。

当第一个查询返回后执行第二个查询

虽然正确的数据被呈现出来,但我们想停止对getPlanet 的第一次调用。

我们用useQuery 中的enabled 选项来解决这个问题。这被设置为一个布尔表达式。当它被设置后,查询将只在它被评估为true 时运行。

我们可以使用enabled 选项,将其设置为字符数据中的homeworld 属性是否有一个值:

const {
  status: planetStatus,
  error: planetError,
  data: planetData,
} = useQuery<Planet, Error>(
  ["planet", { url: characterData?.homeworld }],
  getPlanet,
  {    enabled: !!characterData?.homeworld,  });

现在我们只得到一个关于星球数据的请求。

很好 😊

这篇文章中的代码可以在CodeSandbox中找到:codesandbox.io/s/dependent…