查询取消 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
1const query = useQuery({2 queryKey: ['todos'],3 queryFn: async ({ signal }) => {4 const todosResponse = await fetch('/todos', {5 // 传递可撤销的信号到fetch里去6 signal,7 })8 const todos = await todosResponse.json()9 10 const todoDetails = todos.map(async ({ details } => {11 const response = await fetch(details, {12 // 或这将他传递给好几个实例13 signal,14 })15 return response.json()16 })17 18 return Promise.all(todoDetails)19 }20})使用 axios
使用 axios ^0.22.0
1import axios from "axios";2 3const query = useQuery({4 queryKey: ["todos"],5 queryFn: ({ signal }) =>6 axios.get("/todos", {7 // 传递可撤销的信号到 `axios`8 signal,9 }),10});使用 axios (版本低于 0.22.0)
1import axios from "axios";2 3const query = useQuery({4 queryKey: ["todos"],5 queryFn: ({ signal }) => {6 // 给这个 request 创建一个 CancelToken7 const CancelToken = axios.CancelToken;8 const source = CancelToken.source();9 10 const promise = axios.get("/todos", {11 // 传递这个 token 到该请求12 cancelToken: source.token,13 });14 15 // 如果 React Query 信号是 abort,那么撤销该请求16 signal?.addEventListener("abort", () => {17 source.cancel("Query was cancelled by React Query");18 });19 20 return promise;21 },22});使用 XMLHttpRequest
1const query = useQuery({2 queryKey: ["todos"],3 queryFn: ({ signal }) => {4 return new Promise((resolve, reject) => {5 var oReq = new XMLHttpRequest();6 oReq.addEventListener("load", () => {7 resolve(JSON.parse(oReq.responseText));8 });9 signal?.addEventListener("abort", () => {10 oReq.abort();11 reject();12 });13 oReq.open("GET", "/todos");14 oReq.send();15 });16 },17});使用 graphql-request
使用 graphql-request ^4.0.0
可以传递一个 AbortSignal 给客户端的 request 函数。
1const client = new GraphQLClient(endpoint);2 3const query = useQuery({4 queryKey: ["todos"],5 queryFn: ({ signal }) => {6 client.request({ document: query, signal });7 },8});使用 graphql-request (版本低于 4.0.0)
可以传递一个 AbortSignal 给 GraphQLClient 的构造函数.
1const query = useQuery({2 queryKey: ["todos"],3 queryFn: ({ signal }) => {4 const client = new GraphQLClient(endpoint, {5 signal,6 });7 return client.request(query, variables);8 },9});手动取消
有时,你可能会想手动取消。
例如:请求需要很长时间才能完成,因此用户可以单击“取消”按钮来停止请求。
为此,你只需要调用 queryClient.cancelQueries(key),取消此次查询并将数据还原到上一次的状态。
如果 promise.cancel 可用或者你在查询函数内已经处理了 signal,React Query 就会取消该 Promise 同时取消对应请求。
1const query = useQuery({2 queryKey: ["todos"],3 queryFn: async ({ signal }) => {4 const resp = await fetch("/todos", { signal });5 return resp.json();6 },7});8 9const queryClient = useQueryClient();10 11return (12 <button13 onClick={(e) => {14 e.preventDefault();15 queryClient.cancelQueries(["todos"]);16 }}17 >18 Cancel19 </button>20);