Skip to main content

查询取消 Query Cancellation

React Query 为每个查询函数都提供了一个AbortSignal的实例当且仅当你的运行环境支持该对象。 当一个查询变得过时或者不活跃,那么该“信号”将被中止。 这意味着,所有的查询都是可取消的,而且如果需要,你可以在查询函数中响应取消动作。 这最为方便的一点是,你可以继续使用普通的 async/await 语法,同时获得自动取消的所有好处。 此外,这个解决方案比旧的解决方案更适合 TypeScript。

AbortController API 在大多数运行时环境中都是可用的。但是如果你的运行时环境不支持它,那么查询函数将在对应的地方接收到 undefined。 当然,你可以选择手动实现 AbortController API,在 npm 上有几个可用的

默认行为

正常情况下,未挂载的或在其返回的 Promise 被 resolve/reject 之前的查询是不会被自动取消的。 顺利的话,在 resolve 之后的数据将在缓存中可用,reject 后的 error 可用。 不顺利的话,如果在已经开始接收一个查询且在其结束之前卸载组件,不会产生任何不利影响。并且若再次挂载此组件,且查询还被自动垃圾回收,那么数据可用。

但是,如果你使用了 AbortSignal 或者是给 Promise 附加了一个 cancel 函数,那么这个 Promise 是可以被取消的(例如,下面的使用 fetch 示例)。 此时查询也必须被取消。 取消查询将会导致其恢复到以前的状态。

使用 fetch

const query = useQuery({
queryKey: ['todos'],
queryFn: async ({ signal }) => {
const todosResponse = await fetch('/todos', {
// 传递可撤销的信号到fetch里去
signal,
})
const todos = await todosResponse.json()

const todoDetails = todos.map(async ({ details } => {
const response = await fetch(details, {
// 或这将他传递给好几个实例
signal,
})
return response.json()
})

return Promise.all(todoDetails)
}
})

使用 axios

使用 axios ^0.22.0

import axios from "axios";

const query = useQuery({
queryKey: ["todos"],
queryFn: ({ signal }) =>
axios.get("/todos", {
// 传递可撤销的信号到 `axios`
signal,
}),
});

使用 axios (版本低于 0.22.0)

import axios from "axios";

const query = useQuery({
queryKey: ["todos"],
queryFn: ({ signal }) => {
// 给这个 request 创建一个 CancelToken
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

const promise = axios.get("/todos", {
// 传递这个 token 到该请求
cancelToken: source.token,
});

// 如果 React Query 信号是 abort,那么撤销该请求
signal?.addEventListener("abort", () => {
source.cancel("Query was cancelled by React Query");
});

return promise;
},
});

使用 XMLHttpRequest

const query = useQuery({
queryKey: ["todos"],
queryFn: ({ signal }) => {
return new Promise((resolve, reject) => {
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", () => {
resolve(JSON.parse(oReq.responseText));
});
signal?.addEventListener("abort", () => {
oReq.abort();
reject();
});
oReq.open("GET", "/todos");
oReq.send();
});
},
});

使用 graphql-request

使用 graphql-request ^4.0.0

可以传递一个 AbortSignal 给客户端的 request 函数。

const client = new GraphQLClient(endpoint);

const query = useQuery({
queryKey: ["todos"],
queryFn: ({ signal }) => {
client.request({ document: query, signal });
},
});

使用 graphql-request (版本低于 4.0.0)

可以传递一个 AbortSignalGraphQLClient 的构造函数.

const query = useQuery({
queryKey: ["todos"],
queryFn: ({ signal }) => {
const client = new GraphQLClient(endpoint, {
signal,
});
return client.request(query, variables);
},
});

手动取消

有时,你可能会想手动取消。 例如:请求需要很长时间才能完成,因此用户可以单击“取消”按钮来停止请求。 为此,你只需要调用 queryClient.cancelQueries(key),取消此次查询并将数据还原到上一次的状态。 如果 promise.cancel 可用或者你在查询函数内已经处理了 signal,React Query 就会取消该 Promise 同时取消对应请求。

const query = useQuery({
queryKey: ["todos"],
queryFn: async ({ signal }) => {
const resp = await fetch("/todos", { signal });
return resp.json();
},
});

const queryClient = useQueryClient();

return (
<button
onClick={(e) => {
e.preventDefault();
queryClient.cancelQueries(["todos"]);
}}
>
Cancel
</button>
);