ReactQuery는 현재 가장 인기 있는 서버 상태 관리(캐싱) 라이브러리 중 하나일 것입니다.
ReactQuery는 기존의 요청 관리의 귀찮음과 어려움을 손쉽게 해결하기 위한 라이브러리로, QuickStart 문서는 어떻게 요청을 관리하는지 보여주고 있습니다.
function requestTodos() {
return axios.get("/todos"); // or a different request function that throws on an error
}
// A query provider is a
function Todos() {
const { data, status, error } = useQuery("todos", requestTodos);
if (status === "loading") {
return <span>Loading...</span>;
}
if (status === "error") {
return <span>Error: {error.message}</span>;
}
return (
<div className="list-wrapper">
{data.map((todo) => (
<Todo todo={todo} />
))}
</div>
);
}
요청을 진행하기 위해 useEffect 내에서 axios 콜을 하거나 Redux의 미들웨어를 직접 사용하는 등, 이전의 API 요청 구문에 비해 정말 간단히 요청 데이터와 상태를 관리할 수 있게 되었습니다.
즉, 우리가 반복적으로 작성했던 API의 로딩 상태와 에러 상태 등에 대한 관리를 모두 ReactQuery가 가져가면서 우리가 할 일이 많이 줄어들었고 이것이 사람들이 ReactQuery를 좋아하는 이유라고 생각합니다.
ReactQuery는 요청에 대한 상태를 저장하고 데이터를 관리하는 것 이상의 역할을 하고 있습니다. 데이터 Refetch, preload, cache 등 네트워크 요청과 관련된 전반적인 기능을 제공하고 있습니다.
또한, 전체 React tree에서 쿼리 상태(네트워크 요청 상태)를 공유하므로 캐시 키를 바탕으로 필요할 때 기존의 데이터를 전혀 다른 페이지 또는 컴포넌트에서 요청 없이 사용하거나 캐시 무효화를 통해 데이터를 다시 요청해서 최신 상태로 유지하게끔 API를 다시 호출할 수도 있습니다. 그리고 기존의 요청 상태를 업데이트하는 useMutation 도구를 제공하기도 합니다.
무엇보다 ReactQuery는 서버 상태를 클라이언트 상태와 함께 보관했던 기존의 패러다임을 뒤집고 서버 상태를 별도로 관리해야 한다는 점을 강조하며 새로운 패러다임을 제시하기도 했습니다. 이로인해 우리는 정말 클라이언트 사이드 상태에 집중할 수 있게 되었습니다.
ReactQuery는 useQuery
훅을 호출한 컴포넌트에만 데이터를 전달해줍니다. 그리고 서비스가 복잡할수록 비즈니스 로직을 하나의 컴포넌트로 풀기 어려워집니다. 때문에 관련된 컴포넌트에 데이터를 전달하기 위해서 관련된 최상위 컴포넌트에 useQuery 훅을 호출해서 데이터를 props로 내려주는 방법이 권장되고 있지만, 이 방법은 컴포넌트의 깊이가 깊어질수록 props drilling이 발생합니다.
이를 해결하기 위해 하나의 API를 하나의 custom hook으로 묶는 방법 또는 해당 API 데이터를 사용하는 최상위 컴포넌트에서 Context 또는 그 외 상태관리 라이브러리를 사용하는 것이 해결책으로 등장했습니다. 이로써 우리는 props drilling을 회피하기 위해 서버 상태 관리 스토어 뿐만 아니라 API management hook 또는 API management Context까지 관리해야 합니다.
이 방법의 문제점은 앱 구조가 복잡해질 뿐만 아니라, 앱에서 사용하는 데이터를 한 눈에 알 수 없게 만듭니다. 자식 컴포넌트에서 위와 같은 방법으로 ReactQuery를 사용하고 있다면, 어떤 값을 사용하는지 어떤 값에 사이드 이펙트가 일어날 수 있는지 여부를 컴포넌트 내에서 어떻게 사용되고 있는지 확인하면서 구분지어야 합니다.
서비스가 복잡해질수록 하나의 데이터를 여러 화면에서 보여줘야 하는 상황이 발생합니다. 예를 들어, 필터 기능이 있는 리스트 페이지와 그 데이터를 보여주는 상세 페이지가 존재할 때 필터링된 리스트, 일반 리스트, 상세 페이지 모두 동일한 데이터를 보여줘야 합니다. 한 곳이라도 다른 데이터가 노출된다면(SNS의 좋아요가 리스트에서는 3개, 상세 페이지에서는 2개 등) 이를 보고 데이터 일관성이 깨졌다고 표현하곤 합니다.
ReactQuery는 useQuery를 사용해 API를 호출하면 데이터가 ReactQuery캐시에 저장됩니다. 그리고 getQueryData를 이용해 캐시에서 원하는 값을 불러올 수 있습니다. 이 방식의 장점은 ReactQuery가 필요에 따라 데이터 캐싱 및 refetch 등 복잡도가 높은 부분을 모두 처리해주면서 이러한 고민을 수작업으로 할 필요가 없어진다는 점입니다.
그리고 이 방식의 문제점은 데이터가 업데이트될 때 발생합니다. ReactQuery는 서버의 데이터를 업데이트하는 API 호출을 수행하기 위해 useMutation 훅을 제공하며, onSuccess 콜백 함수에서 ReactQuery의 캐시 데이터를 직접 수정할 수 있습니다. 그리고 이 캐시 데이터를 업데이트하는 가장 쉬운 방법은 서버에서 변경된 데이터를 반환받고 있는 모든 API의 캐시 키에 대해서 invalidateQueries를 호출하는 것입니다. 이 방식은 다음 문제점이 존재합니다.