
TanStack Query(React Query)는 React 애플리케이션에서 서버 상태 관리, 특히 비동기 데이터 페칭을 효율적으로 관리하기 위한 라이브러리입니다. 이를 사용하면 API 요청과 캐싱, 자동 리페치, 에러 핸들링 등을 쉽게 할 수 있습니다. React Query는 데이터 패칭의 복잡성을 줄이고 애플리케이션 상태와 서버 상태 간의 차이를 관리해 주는 역할을 합니다.
(원래는 React Query였으나 v4부터 Tanstack Query로 변경. 아직까지는 React Query라고 부르는 사람들이 대부분.)
💜 탠스택 쿼리 공식문서
https://tanstack.com/query/latest/docs/framework/react/overview
데이터 페칭 및 캐싱: 서버에서 데이터를 가져올 때 React Query가 데이터를 자동으로 캐싱하여, 반복적인 API 요청을 줄이고 필요한 경우 로컬 캐시된 데이터를 사용합니다.
자동 리페치: 서버의 데이터가 변경될 가능성이 있을 때 주기적으로 데이터를 다시 가져오는 기능을 지원합니다.
에러 핸들링: API 요청 실패 시 기본적인 에러 핸들링과 리트라이 기능을 제공하여, 서버 상태를 더 견고하게 관리할 수 있습니다.
로딩 및 에러 상태 관리: 데이터 로딩 상태나 에러 상태를 추적하여 UI에서 바로 사용할 수 있게끔 상태 정보를 제공합니다.
백그라운드 데이터 동기화: 사용자가 다른 작업을 수행 중이거나 앱을 다시 열었을 때 데이터를 자동으로 최신 상태로 동기화할 수 있습니다.
React Query는 상태 관리를 단순화하고 서버 데이터를 다루는 데 드는 시간을 줄여주기 때문에 대규모 애플리케이션에서 특히 유용합니다. 이를 통해 코드의 복잡성을 줄이고 비동기 데이터의 일관성을 유지할 수 있습니다.
예시; useQuery 사용
import React from 'react';
import { useQuery } from 'react-query';
import axios from 'axios';
// 1. 데이터 패칭 함수 작성
const fetchUsers = async () => {
const { data } = await axios.get('/api/users');
return data;
};
function Users() {
// 2. useQuery 훅을 사용하여 데이터 불러오기
const { data, isLoading, isError, error } = useQuery('users', fetchUsers);
// 3. 로딩 상태 처리
if (isLoading) {
return <div>Loading...</div>;
}
// 4. 에러 상태 처리
if (isError) {
return <div>Error: {error.message}</div>;
}
// 5. 데이터가 로드되었을 때 UI 렌더링
return (
<div>
<h1>User List</h1>
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
export default Users;
useQuery 훅: useQuery 훅은 React Query의 주요 기능 중 하나로, useQuery('users', fetchUsers) 형식으로 호출합니다. 여기서 'users'는 캐싱 키이며, fetchUsers 함수는 데이터를 가져옵니다.
✏️ 예제 코드 설명
데이터 패칭 함수 (fetchUsers): axios.get를 사용하여 /api/users 엔드포인트에서 데이터를 가져오는 함수를 정의합니다.
로딩 상태 (isLoading): 데이터가 로딩 중일 때 로딩 메시지를 표시합니다.
에러 상태 (isError): 요청이 실패하면 에러 메시지를 표시합니다.
성공 상태: 데이터가 성공적으로 로드되면, 이를 통해 UI에 목록을 렌더링합니다.
이렇게 React Query를 활용하면, API로부터 데이터를 가져오는 상태와 캐시 관리를 매우 간단히 할 수 있으며, 로딩, 에러, 성공 상태에 따라 UI를 쉽게 조정할 수 있습니다.
useMutation과 useSuspenseQuery는 React Query의 두 가지 훅으로, 각각 서버 데이터 조작과 suspense를 통한 데이터 로딩 관리를 돕습니다. 아래에서 각 훅에 대해 설명하겠습니다.
1. useMutation
useMutation은 데이터 생성, 업데이트, 삭제와 같은 서버의 데이터 조작 작업에 사용됩니다. React Query에서 useQuery가 주로 데이터를 "가져오는" 역할이라면, useMutation은 서버 데이터를 "변경"하는 요청에 사용됩니다.
주요 속성 및 메서드:
예제: 사용자를 새로 생성하는 API 요청
import React from 'react';
import { useMutation, useQueryClient } from 'react-query';
import axios from 'axios';
const createUser = async (newUser) => {
const { data } = await axios.post('/api/users', newUser);
return data;
};
function CreateUser() {
const queryClient = useQueryClient();
// useMutation 훅 사용
const mutation = useMutation(createUser, {
onSuccess: () => {
// 서버 데이터가 업데이트된 후 캐시된 데이터를 다시 가져오기
queryClient.invalidateQueries('users');
},
});
const handleSubmit = () => {
mutation.mutate({ name: 'New User' });
};
return (
<div>
<button onClick={handleSubmit} disabled={mutation.isLoading}>
{mutation.isLoading ? 'Creating...' : 'Create User'}
</button>
{mutation.isError && <p>Error creating user!</p>}
{mutation.isSuccess && <p>User created!</p>}
</div>
);
}
이 예제에서 useMutation은 새로운 사용자를 서버에 생성하는 데 사용됩니다. 요청이 성공하면 queryClient.invalidateQueries('users')를 사용하여 users 키와 관련된 캐시를 무효화하고 데이터를 최신 상태로 유지하도록 합니다.
2. useSuspenseQuery
useSuspenseQuery는 React의 Suspense 기능과 함께 사용하는 useQuery의 대안으로, suspense 옵션이 활성화된 상태에서 데이터를 가져옵니다. Suspense를 사용하면 데이터 로딩 중일 때 로딩 상태를 자동으로 감지하여 적절한 UI를 표시할 수 있습니다. 단, 이 기능을 사용하려면 React 18 이상에서 Suspense를 사용하는 환경이 필요합니다.
특징:
Suspense를 통한 로딩 관리: 로딩 중인 경우 별도의 로딩 상태 체크 없이 Suspense의 fallback UI를 자동으로 표시합니다.
에러 바운더리 필요: useSuspenseQuery를 사용하면 에러 발생 시 앱이 멈추지 않도록 에러 바운더리로 감싸야 합니다.
예제: 사용자 목록을 가져오는 useSuspenseQuery 예제
import React, { Suspense } from 'react';
import { useQuery } from 'react-query';
import axios from 'axios';
const fetchUsers = async () => {
const { data } = await axios.get('/api/users');
return data;
};
function Users() {
const { data } = useQuery('users', fetchUsers, { suspense: true });
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Users />
</Suspense>
);
}
export default App;
위 예제에서 useQuery에 { suspense: true } 옵션을 사용하여 useSuspenseQuery처럼 동작하게 합니다. Suspense 컴포넌트는 데이터가 로딩 중일 때 로딩 UI를 fallback 속성으로 표시합니다. 이처럼 Suspense를 사용하면 isLoading 상태를 따로 확인할 필요가 없어 코드가 간결해집니다.
useQuery와 useMutation의 차이는 각각 "데이터를 가져오는" 요청과 "데이터를 변경하는" 요청을 처리하기 위한 특화된 기능에 있습니다. 두 훅은 역할과 기능이 다르기 때문에 서로의 자리를 대체하기보다는 상황에 맞게 사용하는 것이 중요합니다. 아래에서 useMutation이 필요한 이유와 장점을 설명드릴게요.
1. 서버의 데이터 조작에 특화
useQuery는 서버에서 데이터를 가져오는 읽기 작업에 최적화되어 있으며, 캐싱과 자동 리페치 같은 기능에 강점이 있습니다.
반면 useMutation은 데이터 생성, 업데이트, 삭제 같은 쓰기 작업에 적합하게 설계되어 있습니다. 서버에 변경 사항을 적용하고 나면, 관련된 query들을 다시 가져오거나 (invalidate) 캐시를 업데이트해야 하는 경우가 많은데, useMutation은 이러한 작업을 간편하게 관리할 수 있도록 해줍니다.
2. 데이터 변경 후 자동 캐시 무효화
useMutation의 onSuccess나 onSettled 콜백에서 invalidateQueries 메서드를 호출하여, 특정 쿼리를 다시 가져오도록 쉽게 설정할 수 있습니다.
예를 들어, 새로운 게시글을 추가한 후에 useMutation에서 queryClient.invalidateQueries('posts')를 호출하면, 해당 쿼리 키를 가진 쿼리가 무효화되면서 새로운 데이터로 자동 리페치가 이루어집니다. useQuery로는 이러한 흐름을 자연스럽게 구현하기 어렵습니다.
3. 에러 및 성공 처리에 용이
useMutation은 데이터 변경 작업이 성공했을 때 onSuccess 콜백, 실패했을 때 onError 콜백을 각각 지정할 수 있어 상태에 따른 UI나 추가 동작을 쉽게 구현할 수 있습니다.
예를 들어, 사용자가 글을 작성하고 제출 버튼을 눌렀을 때, 요청 성공 시 성공 메시지를 보여주고 실패 시 에러 메시지를 띄우는 것과 같은 처리를 간편하게 할 수 있습니다.
4. Optimistic Updates (낙관적 업데이트) 지원
useMutation은 낙관적 업데이트(optimistic updates)를 쉽게 설정할 수 있어, 데이터가 실제로 서버에 반영되기 전에 사용자가 즉각적으로 결과를 확인할 수 있도록 합니다. 예를 들어, useMutation으로 댓글을 추가하는 작업을 할 때, 서버 응답이 오기 전에 화면에 댓글이 바로 추가되도록 하여 사용성 향상을 도모할 수 있습니다.
예제: useMutation을 사용하여 게시글 작성
다음은 새로운 게시글을 작성하는 예제입니다. 요청 성공 시 게시글 목록을 자동으로 리페치하도록 설정합니다.
import React from 'react';
import { useMutation, useQueryClient } from 'react-query';
import axios from 'axios';
// 게시글 생성 함수
const createPost = async (newPost) => {
const { data } = await axios.post('/api/posts', newPost);
return data;
};
function NewPostForm() {
const queryClient = useQueryClient();
// useMutation 설정
const mutation = useMutation(createPost, {
onSuccess: () => {
// 게시글 생성 후 'posts' 쿼리를 무효화하여 새로운 데이터를 가져옵니다.
queryClient.invalidateQueries('posts');
},
});
const handleSubmit = () => {
mutation.mutate({ title: 'New Post', content: 'Post content...' });
};
return (
<div>
<button onClick={handleSubmit} disabled={mutation.isLoading}>
{mutation.isLoading ? 'Posting...' : 'Submit'}
</button>
{mutation.isError && <p>Error creating post!</p>}
{mutation.isSuccess && <p>Post created successfully!</p>}
</div>
);
}
✏️ 요약
- 데이터 가져오기 (useQuery): 서버의 데이터를 읽어오는 요청에 적합, 캐싱 및 자동 리페치 기능이 강력함.
- 데이터 변경 (useMutation): 데이터 생성, 수정, 삭제 작업에 특화. 캐시 무효화와 콜백 설정을 통한 후속 작업 처리에 강점이 있으며, 낙관적 업데이트 등 사용자 경험 향상 기능을 제공.
서버의 데이터를 변경해야 하는 경우, useMutation이 적합합니다. useQuery는 데이터 조작에 특화된 기능이 없으므로 데이터 조작에는 useMutation을 사용하는 것이 최적의 선택입니다.