이 글에서는 TanStack Query를 직접 구현하게 된 배경과 그 과정에서 얻게 된 인사이트를 공유한다.
우아한테크코스에서 라이브러리 없이 미션을 수행하면서, 라이브러리를 사용하지 않았을 때의 불편함을 절실히 느꼈다. 이 경험을 통해, 라이브러리는 이러한 불편함을 어떻게 해결해주는지에 관심이 생겼고, 라이브러리를 하나씩 뜯어보는 과정에서 흥미를 느끼기 시작했다.
데이터 패칭 훅을 만들어보면서 이것을 전역적으로 관리하고 싶다는 생각이 들었고, TanStack Query를 직접 구현해봐야겠다는 생각이 들었다. 처음부터 쉽지 않은 라이브러리를 선택한 건 아닌가 하는 생각도 들었지만, 구현을 하면서 새롭게 알게 된 점들이 많았고, 이러한 지식을 정리하고 공유해보고자 한다.
TanStack Query (formerly known as React Query) is often described as the missing data-fetching library for web applications, but in more technical terms, it makes fetching, caching, synchronizing and updating server state in your web applications a breeze.
TanStack Query의 공식 홈페이지 소개 글에서 caching
이란 단어가 눈에 들어왔다.
"캐싱은 어떻게 구현하는 걸까?"라는 호기심이 생겼다. 캐싱이란 '데이터를 어딘가에 저장해 두었다가 필요할 때 다시 사용하는 것'이라고 알고 있었지만, 구체적으로 어떤 방식으로, 어디에 저장하는지가 궁금해졌다. 이를 계기로 TanStack Query의 내부 코드를 들여다보기 시작했다.
그 결과, 캐싱이 자바스크립트 객체에 데이터를 저장하는 단순한 방식이라는 사실을 알게 되었고, 그 단순함에 놀랐다. 하지만 단순 저장만으로 끝나는 것이 아니라, staleTime, gcTime 등의 개념을 통해 캐시를 어떻게 효율적으로 관리할 것인가가 TanStack Query의 핵심이라는 것을 깨달았다.
서버 데이터를 여러 컴포넌트에서 사용할 경우, 상태를 끌어올리는 일이 반복되었다. 이로 인해 props drilling이 발생하고, 이 현상이 심화되면 Context API를 사용할 수밖에 없었다. 서버에서 가져오는 데이터가 많아질 경우 Provider는 늘어나게 되고, 결국 Provider Hell이 발생할 가능성이 높아보였다.
그래서 Provider 없이도 원하는 위치에서 데이터를 사용할 수 있는 전역 데이터 저장소 구조를 설계하고자 했다.
또한, 서버의 데이터를 mutation(수정/추가/삭제)할 때 변경된 데이터를 다시 불러와야 했다. 하지만 이를 위해 refetch 함수를 최상단 컴포넌트에서 props나 Context를 통해 전달해야 했고, 이 과정이 번거롭고 비효율적이라고 느꼈다. 이러한 불편함을 해소하기 위해, 전역 데이터 저장소 구조를 활용하여 mutation 시 자동으로 refetch 될 대상을 명시하는 방식을 구현하고자 했다.
전역 상태 관리 라이브러리들은 대부분 useSyncExternalStore
를 사용하고 있다.
하지만 처음 useSyncExternalStore
를 접했을 때는 그 필요성과 내부 동작 방식이 쉽게 와닿지 않았다. 그래서 이 훅을 직접 사용하여 React 외부 상태와의 안정적인 동기화 방식을 직접 구현해보면서, 그 필요성과 작동 원리를 몸으로 이해하고자 했다.
당연한 이야기지만, TanStack Query 내부 코드를 보면서 처음에는 코드 대부분이 낯설게 느껴졌다. 대략적인 동작 원리를 파악하는 데에도 상당한 시간이 걸렸다.
코드를 분석하면서 느낀 점은, 제공하는 기능이 많고 강력하긴 하지만 구현 방식이 복잡하게 느껴졌다는 것이다. 그래서 직접 구현해보면서, TanStack Query가 어떤 의도로 이러한 구조를 선택했는지 그 배경을 이해하고자 했다.
useQuery
isLoading
, isError
, data
제공useSyncExternalStore
를 이용한 자동 리패칭useMutation
: 데이터를 생성, 수정, 삭제하는 등의 작업에 사용되는 훅onSuccess
/ onError
콜백staleTime
: Stale-While-Revalidate (SWR) 전략 구현gcTime
: Garbage Collection (GC) - 구독이 모두 해제되면 캐시 자동 삭제Request Coalescing
: 동일한 데이터에 대한 중복 요청 방지AbortController
기반 요청 취소 (unmount 시 자동 abort)refetchOnWindowFocus
: 브라우저 포커스 시 자동 fetchrefetchOnReconnect
: 네트워크 재연결 시 자동 fetchrefetchInterval
: polling 방식 주기적 refetchrefetch()
: 수동 리패칭 메서드 제공retry
및 retryDelay
: fetch 실패 시 자동 재시도suspense
removeQueries
, resetQueries
로 전체 초기화cancel
APIuseInfiniteQuery
(무한 스크롤 / 페이지네이션 지원)convertFn
을 통한 데이터 후처리
앞으로 올라올 글들이 기대가 되네요!!