코드의 간소화와 모듈화를 함으로써 보여지는 가독성을 극대화하기 위해 Custom Hook에 React Query를 적용시켜봤다.
아울러 현재 v3 를 적용시켰으나, 두 단계를 점프업해서 v5로 마이그레이션을 해봤다.
v3 에서는 useQuery를 useQuery(key, fn, options)
형식으로 불러왔다면
v5에서는 useQuery를 useQuery({ queryKey, queryFn, ...options })
형식으로 단일 객체
를 전달받아 실행한다.
useMutation
도 그렇고 모든 내장 메서드들은단일 객체
로 받는다고 보면 된다.
📌 파일명 앞에
use
를 붙이는 이유는 에러가 발생하면 React에서 적절한 에러를 나타내기 위함이다.
// App.jsx
const queryClient = new QueryClient();
const App = () => {
return (
<QueryClientProvider client={queryClient}>
<GlobalStyle />
<Router />
</QueryClientProvider>
);
};
최상위 컴포넌트에
QueryClient
를 주입해주는 것 전과 동일하다.
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { addComment, deleteComment, getComments, updateComment } from 'api/firebase';
const QUERY_KEY = 'comments';
export const useComments = () => {
const queryClient = useQueryClient();
// 조회
const { data: comments, isLoading } = useQuery({
queryKey: [QUERY_KEY],
queryFn: getComments
});
// 추가
const addCommentMutation = useMutation({
mutationFn: addComment,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEY] });
}
});
// 수정
const updateCommentMutation = useMutation({
mutationFn: updateComment,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEY] });
}
});
// 삭제
const deleteCommentMutation = useMutation({
mutationFn: deleteComment,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEY] });
}
});
return {
comments,
isLoading,
addComment: addCommentMutation.mutate,
updateComment: updateCommentMutation.mutate,
deleteComment: deleteCommentMutation.mutate
};
};
useComments Hook에서 전부 firebase SDK를 끌어다가 firestore와 비동기 통신을 진행 중이고, 위 v5 문법을 토대로 단일 객체로 받아줬다.
실상은 댓글 컴포넌트에 있는 코드만 옮겨서 마이그레이션 살짝한 게 전부이다.
const { comments, isLoading, addComment, updateComment, deleteComment } = useComments();
단일 객체로 받으면 구조적으로 일관성을 유지할 수 있다.
예를 들어, 쿼리가 성공해도(통신 성공) 데이터가 없을 경우에는 undefined
대신 빈 객체를 반환해줘서 컴포넌트에서 데이터를 처리하는데 일관성을 유지할 수 있다. (불상사 방지 가능)
데이터를 캐시해서 재사용하고 성능을 향상시켜준다. 그리고 단일 객체로 받는 경우 해당 객체의 식별자를 통해 데이터를 캐싱하고 관리하는데 더 효율적이다.
데이터 객체가 null
또는 undefined
인 경우가 아닌 에러 객체를 반환하면 에러 처리를 통일되게 처리할 수 있습니다. 쿼리 실패 시에도 일관된 에러 객체를 반환하여 컴포넌트에서 일관된 방식으로 에러 처리를 할 수 있다.
일부 API 또는 라이브러리가 단일 객체를 반환하는 패턴을 사용할 수 있으며, React Query
가 이러한 패턴을 준수하여 호환성을 유지할 수 있다.
결국, 일관성 있는 데이터 구조를 유지하고, 캐싱이 뛰어나며 통일성 있는 에러처리에 데이터를 반환하는 패턴까지 준수해서 효율성을 최대치로 뽑기위해 단일 객체로 값을 받는다고 생각하면 되겠다.
사실 프로젝트를 하면서 솔직히 너무 궁금했다. 계속 데이터를 확인하려고 console.log
를 여러개 찍었는데 갑자기 콘솔창이 확 올라가는 것이다.
사실 무한루프에 빠졌나라고 잠깐 생각했지만 중간에 멈췄다.
바로 그게 데이터 갱신이었던 것이다.
정의 상, 캐싱은 특정 데이터를 복사본을 저장하고 이후에 데이터의 재접근 속도를 높이는 것을 말한다.
그러면 상황에 맞게 적절하게 데이터 갱신을 해줘야 하는데 언제할까?
위 3가지 시점에서 데이터를 Refetching해준다.
Client와 Server 전역 상태 라이브러리를 온전히 분리해서 사용하니까 되게 효율적이고 Server 데이터를 React Query로 관리함으로써 데이터 처리에 용이하다 보니 Error 횟수도 줄어들었다. 특히 구조적 일관성
을 갖는다는 것이 너무 장점이었다.
Client (Redux Toolkit) : 모달 관련 , 페이지 관련 데이터 상태 등
Server (React Query) : 사용자 정보, 비지니스 로직 관련(비동기 API 호출 데이터) 상태 등