개요
서버 상태 관리 라이브러리라는 것만 인지하고 사용했던 Tanstack Query에 대해 자세히 정리해보려고 한다.
프로젝트를 진행하면서 외부 API로부터 데이터를 불러올 때 Tanstack Query를 자주 사용했었다.
하지만 매번 “이건 이렇게 쓰는 거구나” 수준에서 그쳤고, 이 라이브러리를 왜 사용하는지, 어떤 개념에 기반해 작동하는지에 대한 본질적인 이해는 부족했던 것 같다.
그래서 이번 글에서는 Tanstack Query가 등장하게 된 배경과 핵심 개념을 정리해 보았다.
Tanstack Query를 사용하기 이전의 데이터 패칭 방식
React에서 서버 데이터를 불러올 때, 일반적으로는 다음과 같은 방식으로 작성한다:
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await axios("https://example.com/data");
setData(result.data);
};
fetchData();
}, []);
`useEffect`를 이용해서 컴포넌트가 mount 됐을 때 서버에 있는 데이터를 불러온다.
그다음 `setData`를 이용해서 상태를 저장한다.
만약, 데이터를 불러오고 있는 상태와 데이터를 불러오는데 실패한 상태를 보여주려면 어떻게 해야 될까?
const App = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.get("http://localhost:4000/todos");
setData(response.data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) {
return <div>Loading...</div>
}
if (error) {
return <div>Error: {error.message}</div>
}
return (
<div>
<h1>Fetched Data</h1>
<ul>
{data &&
data.map((item) => (
<li key={item.id}>
<h2>{item.title}</h2>
<p>{item.isDone ? "Done" : "Not Done"}</p>
</li>
))}
</ul>
</div>
);
};
export default App;
로딩 상태, 에러 상태를 useState로 따로 만들어서 관리해야 한다.
이 코드를 사용하는 모든 컴포넌트에 매번 유사한 로직을 반복하게 된다.
지금은 단순하게 데이터를 하나만 관리하는 상황이라서 괜찮게 느껴질지도 모른다.
그런데 만약 프로젝트가 커지고, 서버 데이터를 다루는 컴포넌트가 늘어나면 유지보수의 복잡도가 크게 증가한다.
기존의 데이터 패칭의 문제점
1. 상태 관리가 복잡해짐
2. 중복된 코드가 발생함
3. 비즈니스 로직과 UI 로직을 분리하기 어려움
4. 서버 상태의 관리가 어려움
1️⃣ 상태 관리가 복잡해짐
매번 데이터 패칭, 로딩 상태, 에러 상태 등을 useState로 직접 관리해야 된다.
이로 인해 상태 관리 코드가 길어져서 관리하는데 어려움이 생긴다.
2️⃣ 중복된 코드가 발생함
위에서 봤던 코드를 동일한 데이터를 관리할 때마다 사용하게 된다면 중복된 코드가 굉장히 많아지게 된다.
3️⃣ 비즈니스 로직과 UI 로직을 분리하기 어려움
이곳저곳에 상태를 관리하는 로직이 분산되기 때문에 비즈니스 로직과 UI 로직을 분리하는 작업이 번거로워진다.
4️⃣ 서버 상태의 관리가 어려움
데이터 캐싱, 동기화, 리패칭 등의 기능을 구현하려면 많은 노력과 시간이 필요하기 때문에 굉장히 번거롭다.
Tanstack Query는 바로 이런 비효율적인 데이터 패칭 패턴을 개선하고자 하는 필요에서 비롯되었다.
Tanstack Query의 주요 기능
Tanstack Query의 핵심 개념은 3가지로 요약할 수 있다.
1. 데이터 캐싱
2. 자동 리패칭
3. 쿼리 무효화
1️⃣ 데이터 캐싱
Tanstack Query는 동일한 데이터를 여러 번 요청하지 않도록 클라이언트에 데이터를 캐싱해 둔다.
이를 통해 불필요한 네트워크 요청을 줄이고, 사용자에게 더 빠른 응답 속도를 제공할 수 있다.
2️⃣ 자동 리패칭
Tanstack Query는 서버와 클라이언트의 데이터를 가능한 동기화된 상태로 유지하려 한다.
3️⃣ 쿼리 무효화
서버 데이터가 변경됐을 때, 클라이언트에 캐시 된 데이터 데이터가 더 이상 최신 데이터가 아니라는 것을 알려준다.
이것을 쿼리 무효화라고 하며, 이를 통해 관련된 데이터를 자동으로 다시 가져오게 할 수 있다.
Tanstack Query 사용 방법
Tanstack Query의 주요 기능을 사용하기 위해 3가지의 훅의 사용법을 익히는 것이 좋다.
1. useQuery
2. useMutation
3. invalidateQueries
1️⃣ useQuery - 데이터 조회
서버에서 데이터를 불러올 때 사용하는 훅이다.
더 정확하게 말하면 캐시 컨텍스트에서 캐시 데이터를 가져올 때 사용하는 훅이다.
해당 내용은 Tanstack Query의 동작 원리에서 더 자세히 설명하겠다.
const {data: todos, isPending, isError} = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos
})
`queryKey`: 데이터를 식별하기 위한 고유 키
`queryFn`: 데이터를 불러오는 비동기 함수
`isPending`: 로딩 상태
`isError`: 에러 발생 여부
2️⃣ useMutation - 데이터 변경
데이터를 추가/삭제/수정할 때 사용하는 훅이다.
const {mutate} = useMutation({
mutationFn: addTodo
})
`mutationFn`: 실행할 비동기 함수
3️⃣ invalidateQueries - 쿼리 무효화
특정 쿼리를 무효화하여 데이터를 다시 가져오게 만드는 역할을 한다.
`useMutation`의 onSuccess 콜백에서 자주 사용된다.
const {mutate} = useMutation({
mutationFn: addTodo,
onSuccess: queryClient.invalidateQueries(['todos'])
})
useQuery의 동작 원리
앞서 useQuery는 서버에서 데이터를 가져올 때 사용하는 훅이라고 설명했다.
좀 더 구체적으로, React 컴포넌트 내에서 queryKey를 기반으로 캐시된 데이터를 조회하고, 필요 시 서버에서 데이터를 패칭하는 역할을 담당한다.
이제, useQuery가 내부적으로 어떤 흐름으로 작동하는지 단계별로 살펴보자.
1. 쿼리 키를 기반으로 캐시 컨텍스트에서 캐시 데이터를 요청한다.
queryKey를 통해 현재 요청하려는 데이터가 이미 캐시에 존재하는지 확인한다.
2. 처음에는 저장된 데이터가 없기 때문에 undefined를 반환한다.
queryKey에 해당하는 데이터가 캐시에 없다면 초기 값으로 undefined가 반환된다.
이때 isPending의 값은 true가 된다.
3. queryFn을 호출해서 서버에 데이터를 요청한다.
데이터가 없기 때문에 queryFn에 정의된 함수가 실행되어 서버로부터 데이터를 가져온다.
4. 서버에서 받은 데이터를 캐시 컨텍스트에 저장한다.
서버에서 데이터를 정상적으로 받아오면 해당 데이터는 캐시에 저장된다.
5. 리렌더링이 발생하면서 신규 캐시 데이터를 반환한다.
캐시에 데이터가 저장되면, 컴포넌트가 리렌더링되고 캐시에 있는 최신 데이터를 화면에 보여준다.
isPending은 이제 false가 되고, data에 실제 값이 담긴다.
마무리
이렇게 Tanstack Query의 등장배경, 주요 기능, 사용 방법까지 알아보았다.
조금 더 시간을 투자해서 Tanstack Query의 내부 코드를 이해해보는 시간을 가져야겠다.
[참고 자료]
1. Web: React Query 알아보기(등장 배경, 장점, 기초 개념)
2. TanStack Query(React Query) 핵심 정리