최근 면접에서 Tanstack query가 데이터를 가져올때 병렬로 작동하지는 직렬로 작동하는지에 대한 질문과 개수가 정해지지 않은 리스트에 대한 요청을 위한 hook에 대한 질문에 대해서 제대로 답변하지 못했다.
첫 번째 질문은 알고는 있었지만 막상 답변하려고 생각하니 명확하게 정리되지 않아 모른다고 판단하여 답변하지 못했고, 두 번째 질문은 당시에 의도를 파악하지 못했다.
유독 긴장을 많이 한 부분도 있지만 그보다는 Tanstack query에 대해서 나름 익숙하게 사용한다고, Tanstack query에 대한 내용을 정리하는 것을 소홀히 한 결과라고 생각했다. 이번에 그 착각을 바로 잡을 기회가 생긴것이라 생각하며 두 질문에 대한 내용을 정리한다.
그리고 내가 확실히 알고 설명할 수 있는가에 대한 의심이 가끔은 필요한 것 같다.
tanstack query로 데이터를 가져온다는 것은 useQuery hook을 사용한다는 의미가 된다.
useQuery의 기본 동작은 병렬 요청이다.
const usersQuery = useQuery(['users'], fetchUsers);
const postsQuery = useQuery(['posts'], fetchPosts);
위의 두 쿼리는 동시에 병렬로 요청된다. 이미 useQuery를 사용해 본적이 있다면 당연히 납득이 될 것이다. 연관이 없는 두 데이터를 가져오는데에 굳이 순서를 둘 필요가 없기 때문이다.
하지만 우리는 이미 useQuery가 직렬로도 데이터를 요청한다는 것을 알고 있다.
tanstack query를 사용해 봤다면 특정 쿼리를 불러올때 종속적인 값이 유효한 경우에만 조건부로 불러오기위해 enabled옵션을 사용한 경험이 있을것이다.
const usersQuery = useQuery(['user', userId], () => fetchUser(userId));
const postsQuery = useQuery(
['posts', userQuery.data?.id],
() => fetchPostsByUser(userQuery.data.id),
{
enabled: !!userQuery.data, // userQuery가 성공해야 실행
}
);
퀴리를 위와 같이 사용하는 경우에는 usersQuery가 성공한 이후 postsQuery가 실행되는 직렬로 동작한다.
const usersQuery = useQuery(['users'], fetchUsers);
const postsQuery = useQuery(['posts'], fetchPosts);
위와 같은 예시의 상황에서 suspense mode를 사용하면, 병렬처리가 실패할 가능성이 있다. 첫번째 쿼리가 내부적으로 promise를 던지면 컴포넌트 렌더링이 중단되어, 다른 쿼리들이 실행되기 전에 멈추기 때문이다. 공식문서에서는 이 문제를 해결하는 방법으로 useSuspenseQueries사용하거나 쿼리를 서로다른 컴포넌트로 분리하고 useSuspenseQuery를 사용하는 방법을 권장하고 있다.
const results = useSuspenseQueries({
queries: [
{ queryKey: ['user'], queryFn: fetchUser },
{ queryKey: ['posts'], queryFn: fetchPosts },
],
});
<Suspense fallback={<Loading />}>
<UserData />
</Suspense>
<Suspense fallback={<Loading />}>
<PostData />
</Suspense>
function UserData() {
const { data } = useQuery({
queryKey: ['user'],
queryFn: fetchUser,
});
return <div>User: {data.name}</div>;
}
export default function PostsData() {
const { data } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
});
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
Tanstack query에서 데이터를 가져올때는 코드를 어떻게 작성하느냐에 따라 병렬로 동작하거나 직렬로 동작한다.
개수가 정해지지 않은 리스트라는 것은 렌더링마다 실행해야하는 쿼리의 수가 변화한다는 것이다. 즉 종속성을 지니는 값의 크기를 예측할 수 없는 상황인데 우리는 이럴때 useQueries를 사용했을 것이다. 이를 동적 병렬 쿼리라고 한다.
useQueires는 원하는 만큼의 쿼리를 병렬로 실행하는 hook으로, 쿼리 키를 가진 options 객체를 받아 쿼리 결과 배열을 반환한다. options의 객체의 queries속성에 쿼리 객체 배열을 집어넣으면 된다.
function App({ users }) {
const userQueries = useQueries({
queries: users.map((user) => {
return {
queryKey: ['user', user.id],
queryFn: () => fetchUserById(user.id),
}
}),
})
}
Tanstack query의 데이터 요청은 기본적으로 병렬이지만, suspense mode의 사용 여부에 따라 실패할 수도 있고(대신 suspense mode에서 사용할 hook이 존재), 종속성 값에 따라 직렬로 동작할 수도 있다.(enabled)
또한 쿼리의 개수가 동적인지 여부인 경우를 위한 hook도 존재한다.(useQueries || useSuspenseQueries ) tanstack query를 사용함에 따라 이러한 부분들을 항상 염두하며 개발할 필요가 있다.
References