用React Query取消请求的详细指南

1,010 阅读2分钟

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

以前的文章:

这篇文章涵盖了如何用React Query取消获取请求。我们的实现将允许React Query为我们取消一个请求,如果它的组件被卸载时正在飞行中。我们还将使用户能够点击一个按钮来取消请求。

Cancelling reqeusts

我们的起点

我们将从类似于我们在入门帖中完成的代码开始。

这就是获取函数的样子:

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

  return character;
}
function assertIsCharacter(character: any): asserts character is Character {
  if (!("name" in character)) {
    throw new Error("Not character");
  }
}

这是包含查询的组件:

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

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

在获取数据时,有一个 "取消"按钮被呈现出来。当这个按钮被点击时,我们想取消查询。

提供一个取消请求的方法

上一篇文章介绍了如何用以下方法取消fetch 请求 AbortController.这包含一个可以传递给fetchsignal 属性和一个可以用来取消请求的abort 方法。

让我们在获取函数中的AbortController 及其信号中使用fetch 请求:

interface PromiseWithCancel<T> extends Promise<T> {
  cancel: () => void;
}
function getCharacter(params: Params) {
  const [, { id }] = params.queryKey;
  const controller = new AbortController();
  const signal = controller.signal;
  const promise = new Promise(async (resolve, reject) => {
    try {
      const response = await fetch(`https://swapi.dev/api/people/${id}/`, {
        method: "get",
        signal,
      });
      if (!response.ok) {
        reject(new Error("Problem fetching data"));
      }
      const data = await response.json();
      assertIsCharacter(data);
      resolve(data);
    } catch (ex: unknown) {
      if (isAbortError(ex)) {
        reject(new Error("Request cancelled"));
      }
    }
  });
  (promise as PromiseWithCancel<Character>).cancel = () => {
    controller.abort();
  };
  return promise as PromiseWithCancel<Character>;
}
function isAbortError(error: any): error is DOMException {
  if (error && error.name === "AbortError") {
    return true;
  }
  return false;
}

我们将现有的代码包装在一个新的PromisePromise ,用请求中的数据来解决。如果请求被取消或不成功,该承诺将被拒绝,并出现适当的错误。

我们为返回的Promise 添加了一个cancel 方法,该方法调用AbortController.abort 。我们已经添加了PromiseWithCancel 接口,并在返回的承诺上使用这个接口,否则会发生类型错误。

取消查询

有一个 cancelQueries函数,可以在React Query客户端上调用来取消请求。

首先,我们需要获得一个对查询客户端的引用。我们可以使用useQueryClient 钩子来做到这一点:

import { useQuery, useQueryClient } from "react-query";...
function CharacterDetails() {
  const queryClient = useQueryClient();  ...
}

然后可以在按钮点击事件上使用cancelQueries 函数:

function CharacterDetails() {
  ...
  if (status === "loading") {
    return (
      <button onClick={() => queryClient.cancelQueries("character")}>        Cancel
      </button>
    );
  }
  ...
}

查询的取消键被传入cancelQueries ,在我们的例子中是"character"

如果我们在慢速连接上试一下,我们会看到当取消按钮被点击时,查询被取消了。

Manual cancel

不错😊

当其组件被umounted时,取消飞行中的请求

默认情况下,React Query不会在其组件被挂起时取消飞行中的请求。然而,如果获取函数返回的承诺包含一个cancel 方法,它将调用这个方法,在其组件被卸载时取消一个飞行中的查询。

让我们试一试,在1秒后将组件从渲染树中移除:

export default function App() {
  const [renderComponent, setRenderComponent] = React.useState(true);
  React.useEffect(() => {
    setTimeout(() => {
      setRenderComponent(false);
    }, 1000);
  }, []);
  return <div className="App">{renderComponent && <CharacterDetails />}</div>;
}

在慢速连接的情况下,1秒后获取请求仍在飞行中,让我们看看会发生什么:

Auto cancel

查询在1秒后被取消了,没有点击 "取消"按钮。

爽啊!😊

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