查询取消 query-cancellation
React Query 为每个查询函数都提供了一个AbortSignal的实例,当且仅当你的运行环境支持该对象。
当一个查询变得过时或者不活跃,那么该“信号”将被中止。
这意味着,所有的查询都是可取消的,而且如果需要,您可以在查询函数中响应取消动作。
最好的是,您可以继续使用普通的 async/await 语法,同时获得自动取消的所有好处。
此外,这个解决方案比旧的解决方案更适合 TypeScript。
AbortController API 在大多数运行时环境中都是可用的。但是如果运行时环境不支持它,那么查询函数将在对应的地方接收到 undefined。
当然,你可以选择手动实现 AbortController API,在 npm 上有几个可用的。
注意:该特性在 3.30.0 版本中引入。
如果你仍在使用旧版本的,请尽快升级或者使用旧版本的 cancel 函数
默认行为
默认情况下,未挂载(unmount)的或者在其返回的 Promise 被执行之前的查询是不会取消的。 这意味着,在执行之后的数据是在缓存中可用。 因此,在已经开始接收一个查询时且在其完成之前,卸载其组件的情形是被允许的。并且若再次挂载组件,而查询还没有被 GC,那么数据是可用的。
但是,如果您使用了 AbortSignal 或者是附加了一个 cancel 函数,那么这个 Promise 是可以被取消的(例如,下面的取消 fetch 操作),因此查询也必须被取消。
取消查询将导致其状态恢复到以前的状态。
使用 fetch
1const query = useQuery('todos', async ({ signal }) => {2 const todosResponse = await fetch('/todos', {3 // 传递可撤销的信号到fetch里去4 signal,5 })6 const todos = await todosResponse.json()7 8 const todoDetails = todos.map(async ({ details } => {9 const response = await fetch(details, {10 // 或这将他传递给好几个实例11 signal,12 })13 return response.json()14 })15 16 return Promise.all(todoDetails)17})使用 axios
使用 axios v0.22.0+
1import axios from "axios";2 3const query = useQuery("todos", ({ signal }) =>4 axios.get("/todos", {5 // 传递可撤销的信号到 `axios`6 signal,7 }),8);使用 axios (版本低于 v0.22.0)
1import axios from "axios";2 3const query = useQuery("todos", ({ signal }) => {4 // 给这个 request 创建一个 CancelToken5 const CancelToken = axios.CancelToken;6 const source = CancelToken.source();7 8 const promise = axios.get("/todos", {9 // 传递这个 token 到该请求10 cancelToken: source.token,11 });12 13 // 如果 React Query 信号是 abort,那么撤销该请求14 signal?.addEventListener("abort", () => {15 source.cancel("Query was cancelled by React Query");16 });17 18 return promise;19});使用 XMLHttpRequest
1const query = useQuery("todos", ({ signal }) => {2 return new Promise((resolve, reject) => {3 var oReq = new XMLHttpRequest();4 oReq.addEventListener("load", () => {5 resolve(JSON.parse(oReq.responseText));6 });7 signal?.addEventListener("abort", () => {8 oReq.abort();9 reject();10 });11 oReq.open("GET", "/todos");12 oReq.send();13 });14});使用 graphql-request
使用 graphql-request v4.0.0+
可以传递给 request 函数一个 AbortSignal。
1const client = new GraphQLClient(endpoint);2 3const query = useQuery("todos", ({ signal }) => {4 client.request({ document: query, signal });5});使用 graphql-request (版本低于 v4.0.0)
可以传递给 GraphQLClient 的构造函数一个 AbortSignal.
1const query = useQuery("todos", ({ signal }) => {2 const client = new GraphQLClient(endpoint, {3 signal,4 });5 return client.request(query, variables);6});手动取消
有时,您可能会想手动取消。
例如,如果请求需要很长时间才能完成,此时允许用户单击“取消”按钮来停止请求。
为此,您只需要调用 queryClient.cancelQueries(key),取消此次查询并将数据还原到上一次的状态。
如果 promise.cancel 可用或者你在查询函数内处理了 signal,React Query 将取消该 Promise 的同时取消请求。
1const [queryKey] = useState("todos");2 3const query = useQuery(queryKey, async ({ signal }) => {4 const resp = await fetch("/todos", { signal });5 return resp.json();6});7 8const queryClient = useQueryClient();9 10return (11 <button12 onClick={(e) => {13 e.preventDefault();14 queryClient.cancelQueries(queryKey);15 }}16 >17 Cancel18 </button>19);旧的 cancel 函数
不用担心!旧的取消功能设计将继续工作。
但是我们建议你从可取消的 Promise转移到新的 AbortSignal 接口,因为它已经被标准化为一个通用的构造,用于在大多数浏览器和 Node 中中止正在进行的活动。旧的取消函数可能会在未来的主版本中被删除。
要与此特性集成,请在(需要实现取消的查询)返回的 Promise 上附加一个 cancel 函数。
当查询变得过时或不活跃时,将调用 promise.cancel 函数(如果可用)。
使用 axios
1import axios from "axios";2 3const query = useQuery("todos", () => {4 // 为此请求创建一个新的 CancelToken 源5 const source = axios.CancelToken.source();6 7 const promise = axios.get("/todos", {8 // 传入该源到 cancelToken9 cancelToken: source.token,10 });11 12 // 如果 React Query 调用 `promise.cancel` 方法,则取消请求13 promise.cancel = () => {14 source.cancel("Query was cancelled by React Query");15 };16 17 return promise;18});使用 fetch
1const query = useQuery("todos", () => {2 // 创建一个新的 AbortController 实例3 const controller = new AbortController();4 // 获取 abortController 的信号(signal)5 const signal = controller.signal;6 7 const promise = fetch("/todos", {8 method: "get",9 // 传入信号10 signal,11 });12 13 // 如果 React Query 调用 `promise.cancel` 方法,则取消请求14 promise.cancel = () => controller.abort();15 16 return promise;17});