Query Cancellation 📖
불필요한 네트워크 요청을 제거하기 위해 사용합니다.
대용량 fetching을 중간에 취소하거나 사용하지 않는 컴포넌트에서 fetching이 진행 중이면 자동으로 취소시켜 불필요한 네트워크 비용을 줄일 수 있습니다.
queryFn의 매개변수로 Abort`Signal`을 받을 수 있고 이를 활용해 Query 취소가 가능합니다.
사용 방법
queryFn은 매개변수로 `QueryFunctionContext`객체를 받습니다.
export const getTodos = async (queryFnContext) => {
const { queryKey, pageParam, signal, meta } = queryFnContext;
// queryKey: 배열형태의 쿼리키
// pageParam: useInfiniteQuery 사용 시 getNextPageParam 실행 시 적용
// signal: AbortSignal 을 의미 (네트워크 요청을 중간에 중단시킬 수 있는 장치)
// meta: query에 대한 정보를 추가적으로 메모를 남길 수 있는 string 필드
const response = await axios.get("http://localhost:5000/todos", { signal });
return response.data;
};
useQuery({
queryKey: ["todos"],
queryFn: getTodos,
})
// example: <div onClick={(event) => {}}
컴포넌트가 unmount 될 때 자동으로 Query 취소
import axios from 'axios'
const query = useQuery({
queryKey: ['todos'],
queryFn: ({ signal }) =>
axios.get('/todos', {
// Pass the signal to `axios`
signal,
}),
})
수동으로 Query 취소
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({ queryKey: ['todos'] })
}}
>
Cancel
</button>
)
Optimistic Updates 📖
서버에 요청이 정상임을 가정하고 더 나은 UX를 제공하는 방법이고 낙관적 업데이트라고 합니다.
사용 방법
const addMutation = useMutation({
// 2번
mutationFn: addTodo,
//onSuccess: () => {
// queryClient.invalidateQueries(['todos']);
//},
// 1번
onMutate: async (newTodo) => {
await queryClient.cancelQueries({ queryKey: ['todos'] });
const prevTodos = queryClient.getQueryData(['todos']);
queryClient.setQueryData(['todos'], (old) => [...old, newTodo]);
return { prevTodos };
},
// 3번
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context.prevTodos);
},
// 4번
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
}
})
addMutation.mutate({content})가 호출되는 순간, `addTodo`가 호출될 것이라고 생각하겠지만 아닙니다!
✨ 실행 순서 ✨
1. onMutate
2. mutationFn: addTodo
3. onError
4. onSettled
위 순서대로 실행이 됩니다.
✨ 동작 과정 ✨
1. `onMutate`가 처음 실행되고 `todos`라는 `queryKey`로 데이터를 가져오고 있는 중이라면 사이드 이펙트 문제가 발생할 수 있기 때문에`취소`를 합니다.
await queryClient.cancelQueries({ queryKey: ['todos'] });
2. 현재 가지고 있는 `todos`데이터를 백업 합니다.
const prevTodos = queryClient.getQueryData(['todos']);
3. `setQueryData`를 이용해 현재 가지고 있는 `todos`데이터를 가져와 `newTodo`와 합쳐서 query데이터를 갱신하고 prevTodos를 리턴합니다.
queryClient.setQueryData(['todos'], (old) => [...old, newTodo]);
return { prevTodos }
❗위 과정은 서버에 갔다 온 로직이 아니고 클라이언트에서 todoList를 갱신하는 로직입니다.
4. 리턴 받은 `prevTodos`는 `onError`의 context로 들어가게 됩니다.
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context.prevTodos);
}
5. `addTodo`가 성공했다면 `invalidateQueries`를 통해서 데이터를 최신 상태로 유지합니다.
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
}
Prefetching 📖
페이지 이동 전에 이동할 페이지의 쿼리를 백그라운드에서 미리 호출합니다. 캐시 데이터가 있는 상태로 해당 페이지로 이동 시 로딩 없이 바로 UI를 볼 수 있습니다.
const prefetchTodos = async () => {
// prefetch 할 queryKey와 queryFn 은 이동할 페이지의 쿼리와 동일해야 적절합니다.
await queryClient.prefetchQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})
}
Paginated / Lagged Queries 📖
다른 페이지를 클릭 시 매번 Loading UI를 보여주기보다는 기존 UI를 유지하다가 서버로부터 새로운 데이터를 받아왔을 때 바꾸는 방식입니다.
`keepPreviousData`속성을 true로 하게 되면 원래 가지고 있는 데이터를 유지하다가 새로운 데이터를 가져오면 바꿔주겠다는 의미입니다.
const { data: moives, inPending } = useQuery({
queryKey: ['movies', page],
queryFn: fetchMovieData,
keepPreviousData: true,
})
Infinite Queries 📖
Data Fetching이 일어날 때마다 기존 리스트 데이터에 fetched data를 추가하고자 할 때 유용하게 사용할 수 있는 훅입니다.
더 보기 UI 또는 무한스크롤 UI에 사용하기 적합합니다.
사용 방법
const fetchProjects = async ({ pageParam = 0 }) => {
const res = await fetch('/api/projects?cursor=' + pageParam)
return res.json()
}
const {
data,
error,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage,
status,
} = useInfiniteQuery({
queryKey: ['projects'],
queryFn: fetchProjects,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})
실행 순서
1. queryFn 실행
2. 캐시 데이터 등록 { pages, pageParams }
3. getNextPageParam 실행 (리턴된 NextPageParam은 훅 내부 메모리에 저장. 캐시 x)
4. NextPageParam이 undefined가 아니라면 hasNextPage `true`로 변경
5. fetchNextPage 실행
6. queryFn 실행 (이때 내부적으로 저장되어 있던 NextPageParam을 queryFn의 매개변수로 넘겨줌)
Observer
`react-intersection-observer`를 설치합니다.
yarn add react-intersection-observer
`threshold`속성을 사용해 observer가 보이게 되면 다음 페이지를 요청합니다.
const { ref } = userInView({
threshold: 1,
onChange: (inView) => {
if(!inView || !hasNextPage || isFetchingNextPage) return;
fetchNextPage();
}
})
이전 게시물
출처 🏷️
'공부 > 상태 관리' 카테고리의 다른 글
전역 상태 관리, 정말 필요한가? (0) | 2024.12.10 |
---|---|
Zustand 동작 원리 알아보기 (1) | 2024.09.06 |
Zustand 알아보기 (0) | 2024.09.06 |
TanStack Query 알아보기 2 (동작 원리) (0) | 2024.09.06 |
TanStack Query 알아보기 1 (기본 사용법) (0) | 2024.09.06 |