공식문서 : https://tanstack.com/query/v4/docs/guides/query-cancellation
React Query는 런타임 환경에서 사용가능한 경우 AbortSignal
인스턴스와 함께 각 쿼리 함수를 제공한다. 쿼리가 오래되거나 inactive 되면, signal
이 abort가 된다. 즉, 모든 쿼리를 취소할 수 있으며 원하는 경우 쿼리 함수 내에서 취소에 응답할 수 있다. 이것의 가장 좋은 점은 자동 취소의 이점을 모두 얻으면서도 일반 async/await 구문을 계속 사용할 수 있다는 것이다. 또한, 이 솔루션은 이전 솔루션보다 Typescript에서 더 잘 작동한다.
AbortController
API는 대부분의 런타임 환경에서 사용할 수 있지만, 런타임 환경에서 지원하지 않는 경우 쿼리함수가 undefined
을 대신 수신한다. 원하는 경우 AbortController
API를 폴리필(웹브라우저상에서 지원하지 않는 기능을 구현)할 수 있고, 이용가능한 여러가지가 있다.
기본적으로 promise들이 resolve되기 전에 unmount되거나 사용되지 않게 된 쿼리는 취소되지 않는다. 즉, promise가 resolve된 후 결과 데이터를 캐시에서 사용할 수 있음을 의미한다. 이는 쿼리를 수신하기 시작했지만 완료되기 전에 컴포넌트를 unmount한 경우 유용하다. 컴포넌트를 다시 mount하고 쿼리가 아직 garbage collect되지 않은 경우에는, 데이터를 사용할 수 있다.
그러나, AbortSignal
을 사용하거나 cancel
함수를 Promise에 부착하면, Promise가 취소되므로(예: fetch 중단), Query도 취소되어야한다. 쿼리를 취소하면 해당 state가 이전 state로 돌아간다.
fetch
const query = useQuery(['todos'], async ({ signal }) => {
const todosResponse = await fetch('/todos', {
// Pass the signal to one fetch
signal,
})
const todos = await todosResponse.json()
const todoDetails = todos.map(async ({ details } => {
const response = await fetch(details, {
// Or pass it to several
signal,
})
return response.json()
})
return Promise.all(todoDetails)
})
axios
v0.22.0+import axios from 'axios'
const query = useQuery(['todos'], ({ signal }) =>
axios.get('/todos', {
// Pass the signal to `axios`
signal,
})
)
XMLHttpRequest
const query = useQuery(['todos'], ({ 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()
})
})
쿼리를 수동으로 취소할 수 있다. 예를들어, 요청을 완료하는데 오랜 시간이 걸리는 경우 유저가 취소 버튼을 클릭하여 요청을 중지할 수 있게 할 수 있다. 이렇게 하려면 쿼리를 취소하고 이전 상태로 되돌리는 queryClient.cancelQueries(key)
를 호출하기만 하면 된다. promise.cancel
을 사용할 수 있거나 쿼리 함수에 전달된 signal
를 사용했다면, React Query는 Promise도 추가로 취소한다.
const query = useQuery(['todos'], 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>
)