일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- STATE
- tanstack query
- 프론트엔드 개발
- 상태 관리 라이브러리
- React Query
- 프로젝트캠프
- Server State
- 스나이퍼팩토리
- React
- 수코딩
- 리액트프로젝트
- sucoding
- frontend
- TypeScript
- 유데미
- 개발
- 공식문서
- 웅진씽크빅
- 프론트엔드
- Today
- Total
yunicornlab
[React Query] Tanstack Query 공식문서 - 개요 본문
https://tanstack.com/query/latest/docs/framework/react/overview
Overview | TanStack Query React Docs
TanStack Query (FKA 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 serve...
tanstack.com
It makes fetching, caching, synchronizing and updating server state
in your web applications a breeze.
대부분의 전통적인 상태 관리 라이브러리는 Client State에는 적합하지만, 비동기나 서버 상태에는 그렇게 적합하지는 않다.
서버 상태는 아래의 특징처럼 완전히 다르기 때문이다.
Server State:
This is because server state is totally different.
For starters, server state:
- Is persisted remotely in a location you may not control or own.
- Requires asynchronous APIs for fetching and updating.
- Implies shared ownership and can be changed by other people without your knowledge.
- Can potentially become "out of date" in your applications if you're not careful.
Is persisted remotely in a location you may not control or own
(서버 상태는 원격에 저장되며, 당신이 통제하거나 소유하지 않을 수도 있다)
- 서버 상태는 일반적으로 데이터베이스(DB)나 외부 API에서 관리되기 때문에, 이 데이터가 어디에 저장되고 어떻게 관리되는지 직접 통제할 수 없는 경우가 많다.
예를 들어, Firebase, Supabase, AWS, 또는 타사 API를 사용하는 경우, 그 데이터를 직접 소유하거나 관리하지 못할 수도 있다. - 만약 사용자가 Supabase에서 북마크 데이터를 가져온다면, Supabase 서버가 데이터를 관리하며, 프론트엔드 개발자는 직접 데이터베이스를 운영하는 것이 아니라 API를 통해 접근한다.
Requires asynchronous APIs for fetching and updating
(서버 상태를 가져오고 업데이트하려면 비동기 API가 필요하다)
- 서버 상태는 클라이언트(React 애플리케이션)가 직접 가지고 있는 게 아니라, 네트워크 요청을 통해 가져와야 하고,
데이터를 불러오거나 수정할 때는 fetch, axios 등의 비동기 API 요청을 사용해야 한다.
네트워크 요청은 응답을 기다려야 하고, 실패할 가능성도 있기 때문에 관리가 필요하다. - 아래처럼 데이터를 가져올 때 fetch를 사용하고, await을 통해 응답을 기다리는 코드를 작성해볼 수 있다.
const { data, error, isLoading } = useQuery({
queryKey: ['posts'],
queryFn: async () => {
const response = await fetch('/api/posts');
return response.json();
},
});
Implies shared ownership and can be changed by other people without your knowledge
(공유된 상태이므로 다른 사람들이 변경할 수 있으며, 이를 모를 수도 있다)
- 서버 상태는 여러 사용자가 공유할 수 있다.
서버 상태는 클라이언트에서 직접 관리하는 게 아니라, 서버에서 변경될 수 있기 때문에 실시간으로 업데이트되지 않으면 데이터가 오래된 상태가 될 수 있다. - 예를 들어, A 사용자가 게시글을 수정했는데, B 사용자의 애플리케이션에서는 여전히 이전 데이터를 보고 있을 수 있다.
이를 해결하기 위해 refetch() 또는 invalidateQueries()를 사용해 데이터를 자동으로 새로 불러와서 최신 상태로 유지하는 방법을 사용할 수 있다.
const queryClient = useQueryClient();
mutation.mutate(updatedPost, {
onSuccess: () => {
queryClient.invalidateQueries(['posts']);
},
});
Can potentially become "out of date" in your applications if you're not careful
(주의하지 않으면 애플리케이션에서 서버 상태가 오래된 상태가 될 수 있다)
- 서버 상태는 계속 변할 수 있는데, 클라이언트가 이를 반영하지 않으면 오래된 데이터를 사용하게 된다.
네트워크 요청이 성공하더라도, 이후 서버에서 데이터가 바뀌면 클라이언트는 최신 상태를 반영하지 못할 수 있다.
React Query를 사용하면 자동으로 데이터를 동기화하거나 캐시를 업데이트하는 등의 기능을 활용할 수 있다. - 예를 들어, 서버에서 게시글 목록을 받아왔는데, 다른 사용자가 게시글을 추가하면, 기존 데이터는 오래된 상태가 된다.
이를 방지하기위해 staleTime, refetchOnWindowFocus 등을 설정할 수 있다.
const { data } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 5000, // 5초 동안은 데이터를 신뢰함 (이후 다시 불러올 수 있음)
refetchOnWindowFocus: true, // 창을 다시 포커스하면 데이터 다시 가져옴
});
이러한 특징을 가진 서버 상태를 관리하기 위해 생기는 여러 가지 과제들!
Once you grasp the nature of server state in your application, even more challenges will arise as you go, for example:
- Caching... (possibly the hardest thing to do in programming)
- Deduping multiple requests for the same data into a single request.
- Updating "out of date" data in the background.
- Knowing when data is "out of date".
- Reflecting updates to data as quickly as possible.
- Performance optimizations like pagination and lazy loading data.
- Managing memory and garbage collection of server state.
- Memoizing query results with structural sharing
Caching... (possibly the hardest thing to do in programming)
(캐싱... 아마도 프로그래밍에서 가장 어려운 것 중 하나)
➡︎ 데이터 요청을 줄이고 성능 최적화
- 데이터를 요청할 때마다 서버에서 가져오는 것은 비효율적이므로 캐싱을 사용해 성능을 최적화해야 한다.
하지만 캐싱한 후에 다음 데이터 요청까지 너무 오래 걸리면 캐싱된 데이터가 stale(오래된) 데이터가 될 수 있고,
반대로 데이터를 너무 자주 요청하게 되면 캐싱의 이점이 사라지므로, 캐시를 얼마나 유지할 지 생각해야한다. - React Query의 cacheTime, staleTime을 조절해서 캐시가 얼마나 유지될지를 설정할 수 있다.
useQuery(['posts'], fetchPosts, {
staleTime: 5000, // 5초 동안 캐싱된 데이터를 신뢰
cacheTime: 10000, // 10초 후 캐시 삭제
});
Deduping multiple requests for the same data into a single request
(동일한 데이터를 요청하는 여러 개의 요청을 하나의 요청으로 합치기)
➡︎ 중복된 네트워크 요청을 하나로 합치기
- 여러 개의 컴포넌트가 동일한 데이터를 요청하면 불필요한 네트워크 요청이 발생할 수 있다.
이를 방지하기 위해 deduplication (중복 제거) 기법으로 중복 요청을 줄일 수 있다. - React Query에서는 동일한 queryKey를 가진 요청이 동시에 여러 번 발생하면, React Query가 하나의 요청만 보낸 후에 결과를 공유한다.
const { data } = useQuery(['user', userId], fetchUserData);
Updating "out of date" data in the background
(오래된 데이터를 백그라운드에서 업데이트하기)
➡︎ 백그라운드에서 최신 데이터 가져오기
- 캐싱된 데이터가 오래된 경우, 사용자가 현재 데이터를 보면서도 백그라운드에서 최신 데이터를 가져와야 한다.
이를 통해서 사용자는 UI가 깜빡이지 않고 데이터를 최신 상태로 유지할 수 있다. - React Query에서 refetchOnMount, refetchOnWindowFocus 등을 사용할 수 있다.
useQuery(['posts'], fetchPosts, {
refetchOnMount: true, // 컴포넌트가 마운트될 때 다시 가져옴
refetchOnWindowFocus: true, // 사용자가 브라우저 탭을 다시 열면 자동으로 데이터 새로고침
});
Knowing when data is "out of date"
(데이터가 오래된 시점을 아는 것)
➡︎ 데이터가 오래되었는지 판별
- "오래된 데이터"란 무엇인지 정의하는 것이 중요하다.
예를 들어, 실시간 채팅 데이터는 몇 초마다 갱신해야 하지만, 사용자 프로필 데이터는 몇 분 동안 유지해도 문제없다. - React Query에서는 staleTime 옵션을 사용해 오래되었다는 시점을 설정할 수 있다.
useQuery(['notifications'], fetchNotifications, {
// 10초 동안은 캐싱된 데이터를 사용하지만, 이후에는 다시 요청
staleTime: 10000,
});
Reflecting updates to data as quickly as possible
(데이터 업데이트를 최대한 빠르게 반영하기)
➡︎ UI에서 데이터 즉시 반영
- 데이터가 수정되면 UI에 즉시 반영해야 하는데, 서버 응답을 기다리면 반응성이 떨어지기 때문에 Optimistic Update (낙관적 업데이트)를 활용할 수 있다.
- React Query에서 onMutate를 활용해, 서버 요청이 완료되기 전에 UI에서 먼저 데이터를 변경하는 방식을 사용할 수 있다.
const mutation = useMutation(addComment, {
onMutate: (newComment) => {
queryClient.setQueryData(['comments'], (old) => [...old, newComment]); // 즉시 UI에 반영
},
});
Performance optimizations like pagination and lazy loading data
(페이징 및 지연 로딩 같은 성능 최적화 기법 적용하기)
➡︎ 페이지네이션, 지연 로딩으로 성능 개선
- 한 번에 너무 많은 데이터를 가져오면 성능이 저하되므로, 페이지 단위로 데이터를 가져오거나, 필요한 순간에만 로드해야 한다.
- React Query에서 useInfiniteQuery 활용해 무한 스크롤을 구현할 수 있다.
const { data, fetchNextPage } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor, // 다음 페이지 정보
});
Managing memory and garbage collection of server state
(서버 상태의 메모리 및 가비지 컬렉션 관리하기)
➡︎ 불필요한 데이터 정리
- 클라이언트에서 불필요한 데이터를 계속 유지하면 메모리 낭비가 심해질 수 있음.
오래된 데이터는 일정 시간이 지나면 제거하는 것이 중요함. - React Query에서 cacheTime 활용
useQuery(['users'], fetchUsers, {
// 60초 동안 캐싱된 데이터를 유지하지만, 이후에는 자동으로 메모리에서 제거된다.
cacheTime: 60000,
});
Memoizing query results with structural sharing
(구조적 공유를 이용해 쿼리 결과를 메모이징하기)
➡︎ 기존 데이터를 재사용하여 렌더링 최적화
- 동일한 데이터를 여러 개의 컴포넌트에서 공유할 때, 불필요한 리렌더링을 방지하고 성능을 최적화해야 한다.
- React Query는 "구조적 공유(Structural Sharing)" 기법을 사용하여 기존 데이터를 효율적으로 재사용한다.
useQuery가 실행될 때, 이전 데이터와 새로운 데이터를 비교해서 변경된 부분만 업데이트 한다. - 예를 들어, 아래 예시 코드에서 data의 배열이 동일한 경우, React Query는 자동 메모이징을 활용해 새로운 배열을 생성하지 않고 기존 배열을 재사용함으로써 불필요한 리렌더링을 방지한다.
const { data } = useQuery(['todos'], fetchTodos, {
initialData: [], // 초기 데이터 설정
});
React Query가 제공하는 기술적 이점
On a more technical note, React Query will likely:
- Help you remove many lines of complicated and misunderstood code from your application and replace with just a handful of lines of React Query logic.
- Make your application more maintainable and easier to build new features without worrying about wiring up new server state data sources.
- Have a direct impact on your end-users by making your application feel faster and more responsive than ever before.
- Potentially help you save on bandwidth and increase memory performance.
Help you remove many lines of complicated and misunderstood code from your application and replace with just a handful of lines of React Query logic.
(복잡하고 이해하기 어려운 코드를 줄이고, React Query의 간결한 로직으로 대체할 수 있다.)
➡︎ 코드 단순화
- 기존에는 useEffect와 useState를 사용하여 데이터를 가져오고 관리해야 했는데, 데이터 로딩, 에러 처리, 상태 업데이트 등을 직접 관리해야해서 해당 로직이 많아지고 코드가 복잡해졌다.
하지만 React Query를 사용하면 네트워크 요청과 캐싱 및 에러 처리 등을 자동으로 해주므로 몇 줄의 코드로 간결하게 작성할 수 있다.
- 기존 방식 (useEffect + useState 사용)
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const res = await fetch('/api/posts');
const json = await res.json();
setData(json);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
- React Query 방식 (useQuery 사용)
const { data, error, isLoading } = useQuery(['posts'], fetchPosts);
Make your application more maintainable and easier to build new features without worrying about wiring up new server state data sources.
(새로운 서버 데이터 소스를 연결하는 데 신경 쓰지 않고, 유지보수가 쉽고 확장하기 쉬운 애플리케이션을 만들 수 있다.)
➡︎ 유지보수성 향상
- 서버에서 데이터를 가져올 때 API 요청 로직을 분리하지 않으면 코드가 복잡해지고 유지보수가 어려워진다.
- React Query는 데이터를 중앙에서 관리하기 때문에, 새로운 기능을 추가할 때 기존 데이터 로직을 수정하지 않아도 된다.
- 예를 들어, 새로운 users API를 추가하는 상황에서 React Query가 없으면 기존 useEffect 로직을 복사/수정해야 하지만,
React Query를 사용하면 단순히 새로운 useQuery 훅을 추가하면 된다. - 즉, API 요청 로직이 컴포넌트와 분리되기 때문에 유지보수가 훨씬 쉬워짐.
const { data: users } = useQuery(['users'], fetchUsers);
const { data: posts } = useQuery(['posts'], fetchPosts);
Have a direct impact on your end-users by making your application feel faster and more responsive than ever before.
(애플리케이션이 훨씬 빠르고 반응성이 좋아져, 최종 사용자에게 직접적인 영향을 미친다.)
➡︎ 속도 개선
- React Query의 자동 캐싱과 백그라운드 데이터 업데이트 덕분에 API 요청을 최소화할 수 있다.
즉, 데이터를 불러오는 속도가 빨라지고, UI가 더 즉각적으로 반응할 수 있게 된다. - 예시) 캐싱을 활용해 속도 개선 : 데이터를 다시 가져오는 대신 캐싱된 데이터를 즉시 사용해서 UI가 빠르게 로딩된다.
useQuery(['posts'], fetchPosts, {
staleTime: 10000, // 10초 동안은 캐싱된 데이터 사용
});
- 예시) 백그라운드 업데이트로 부드러운 UI 제공 : 사용자가 새로고침을 하지 않아도 최신 데이터를 자동으로 가져온다.
useQuery(['notifications'], fetchNotifications, {
refetchOnWindowFocus: true, // 창을 다시 열면 자동으로 최신 데이터 불러옴
});
Potentially help you save on bandwidth and increase memory performance.
(네트워크 대역폭을 절약하고 메모리 성능을 향상시킬 수도 있다.)
➡︎ 네트워크/메모리 최적화
- 서버에서 데이터를 가져올 때 불필요한 요청을 줄이면 네트워크 트래픽을 절약할 수 있고,
React Query는 오래된 데이터를 자동으로 가비지 컬렉션(GC)해서 메모리 성능을 최적화해준다. - 예시) 불필요한 API 호출 방지 : 사용자가 1분 내에 같은 데이터를 여러 번 요청해도 서버에 불필요한 요청이 발생하지 않는다.
useQuery(['userProfile'], fetchUserProfile, {
staleTime: 60000, // 1분 동안은 새로운 요청을 보내지 않음
});
- 예시) 가비지 컬렉션으로 메모리 관리 : 오래된 데이터를 자동으로 제거하여 메모리를 절약할 수 있다.
useQuery(['messages'], fetchMessages, {
cacheTime: 300000, // 5분 후 캐시된 데이터를 삭제하여 메모리 관리
});
Example Code
import {
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
function Example() {
const { isPending, error, data } = useQuery({
queryKey: ['repoData'],
queryFn: () =>
fetch('https://api.github.com/repos/TanStack/query').then((res) =>
res.json(),
),
})
if (isPending) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{' '}
<strong>✨ {data.stargazers_count}</strong>{' '}
<strong>🍴 {data.forks_count}</strong>
</div>
)
}