์ React Query์ธ๊ฐ
- ์๋ฒ ์ํ๋ ํด๋ผ์ด์ธํธ ์ํ์ ๋ฌ๋ฆฌ "์๊ฒฉยท์ ํํ์ง ์์ยท๊ณต์ ๋จยท์บ์ ๊ฐ๋ฅ" ํน์ฑ์ ๊ฐ์ง๋ค. React Query๋ ์ด๋ฅผ ์ํ ํ์คํ๋ ํจํด(ํจ์นญยท์บ์ฑยท๋๊ธฐํยท๋ฌดํจํ)์ ์ ๊ณตํด ์ค๋ณต ์์ฒญ, ๋ก๋ฉ/์๋ฌ ์ฒ๋ฆฌ, ์ฌ์๋, ๋ฐฑ๊ทธ๋ผ์ด๋ ๊ฐฑ์ ์ ์๋ํํ๋ค.
ํต์ฌ ๊ฐ๋
ํ๋์
- Query(useQuery): ์ฝ๊ธฐ. ์๋ ์บ์ฑ, ์ค๋ณต ์์ฒญ ๋ณํฉ, ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฆฌํจ์น
- Mutation(useMutation): ์ฐ๊ธฐ. ๋๊ด์ ์
๋ฐ์ดํธ, ์คํจ ๋กค๋ฐฑ, ์ฑ๊ณต ์ ๋ฌดํจํ
- ํค(queryKey): ์๋ฒ ๋ฐ์ดํฐ์ ์๋ณ์(๋ฐฐ์ด ํํ). ์์ ์ ์ด๊ณ ๊ฒฐ์ ์ ์ด์ด์ผ ํจ
- ์ ์ ๋/์๋ช
:
staleTime(์ ์ ํ ๊ธฐ๊ฐ), gcTime(๋ฉ๋ชจ๋ฆฌ์ ๋จ์์๋ ๊ธฐ๊ฐ, v5)
- ๋ฌดํจํ:
queryClient.invalidateQueries({ queryKey })๋ก ๊ด๋ จ ์ฟผ๋ฆฌ ์ฌ์กฐํ
์ค์น/์ด๊ธฐ ์ค์
npm i @tanstack/react-query
// app/providers.tsx ๋๋ _app.tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
}
์กฐํ: useQuery ๊ธฐ๋ณธ
import { useQuery } from "@tanstack/react-query";
function useTodos() {
return useQuery({
queryKey: ["todos"],
queryFn: () => fetch("/api/todos").then((r) => r.json()),
staleTime: 60_000, // 1๋ถ๊ฐ ์ ์ โ ํฌ์ปค์ค ์ ์ฌํจ์น ์ ํจ
retry: 2, // ์คํจ ์ 2ํ ์ฌ์๋
refetchOnWindowFocus: true,
select: (data) => data.items, // ์๋ฒ ์๋ต ํ ์ ํ ๋ณํ
});
}
๋ณ๊ฒฝ: useMutation + ๋ฌดํจํ/๋๊ด์ ์
๋ฐ์ดํธ
import { useMutation, useQueryClient } from "@tanstack/react-query";
function useAddTodo() {
const qc = useQueryClient();
return useMutation({
mutationFn: (title: string) =>
fetch("/api/todos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title }),
}).then((r) => r.json()),
onMutate: async (title) => {
await qc.cancelQueries({ queryKey: ["todos"] });
const prev = qc.getQueryData<any>(["todos"]);
qc.setQueryData<any>(["todos"], (old) => ({
...old,
items: [...(old?.items ?? []), { id: "temp", title }],
}));
return { prev };
},
onError: (_e, _v, ctx) => {
if (ctx?.prev) qc.setQueryData(["todos"], ctx.prev); // ๋กค๋ฐฑ
},
onSuccess: () => qc.invalidateQueries({ queryKey: ["todos"] }),
});
}
ํค ์ค๊ณ ๋ฒ ์คํธ ํ๋ํฐ์ค
- ๋ฐฐ์ด ๊ธฐ๋ฐ(์:
['post', postId]), ์์ยทํ์
์ผ๊ด์ฑ ์ ์ง
- ์กฐ๊ฑด๋ถ ํ๋ผ๋ฏธํฐ๋ ๋ช
์์ ์ผ๋ก ํฌํจ(ํ์ด์ง๋ค์ด์
, ํํฐ)
- ๋ถ์์ ํ ๊ฐ์ฒด ์์ฑ ์ง์(๋ฉ๋ชจ์ด์ ์ด์
๋๋ ์์ ์ ์ง๋ ฌํ)
์ ์ ๋ ๋ชจ๋ธ(staleTime vs gcTime)
staleTime: ์ ์ ํ ๋์ ์ฌํจ์น ํธ๋ฆฌ๊ฑฐ(ํฌ์ปค์ค/๋คํธ์ํฌ ๋ณต๊ตฌ)๊ฐ ๋ฌด์๋จ
gcTime(v5): ์ฌ์ฉ๋์ง ์๋ ์บ์๊ฐ ๋ฉ๋ชจ๋ฆฌ์์ ์ ๊ฑฐ๋๊ธฐ๊น์ง์ ์๊ฐ(์ด์ v4์ cacheTime)
์์ฃผ ์ฐ๋ ์ต์
์๋ น
placeholderData: ์ต์ด ๋ก๋ฉ ์ ์ฆ์ ํ์ํ ์์ ๋ฐ์ดํฐ
keepPreviousData: ํ์ด์ง๋ค์ด์
์ ํ ์ ์ด์ ๋ฐ์ดํฐ ์ ์ง๋ก ๊น๋นก์ ๊ฐ์
enabled: ์กฐ๊ฑด ์ถฉ์กฑ ์์๋ง ์คํ(์์กด ๋ฐ์ดํฐ ๋ก๋ ํ ๋ฑ)
์์กด/๋ณ๋ ฌ/๋ฌดํ ์ฟผ๋ฆฌ
// ์์กด ์ฟผ๋ฆฌ: userId๊ฐ ์์ ๋๋ง
useQuery({ queryKey: ["user", id], queryFn: fetchUser, enabled: !!id });
// ๋ฌดํ ์คํฌ๋กค
import { useInfiniteQuery } from "@tanstack/react-query";
useInfiniteQuery({
queryKey: ["feed"],
queryFn: ({ pageParam = 0 }) =>
fetch(`/api/feed?page=${pageParam}`).then((r) => r.json()),
getNextPageParam: (last) => last.nextPage ?? undefined,
});
์ค๋ฅยท๋ก๋ฉ UX
- ๊ธ๋ก๋ฒ: Suspense/์๋ฌ ๋ฐ์ด๋๋ฆฌ, ํ ์คํธ/์ค๋ต๋ฐ๋ก ํผ๋๋ฐฑ
- ๋ก์ปฌ:
isLoading, isFetching, isError, error๋ก ์ํ ์ธ๋ถํ
- ์ฌ์๋/๋ฐฑ์คํ: ๋คํธ์ํฌ ์๋ฌ๋ง ์ฌ์๋ํ๊ณ ๋น์ฆ๋์ค ์๋ฌ๋ ์ฆ์ ๋
ธ์ถ
SSR/ISR Hydration(Next.js)
// ์๋ฒ: ํ๋ฆฌํ์น ํ ํ์(dehydrate)
import { QueryClient, dehydrate } from "@tanstack/react-query";
export async function generateMetadata() {
/* ... */
}
export default async function Page() {
const qc = new QueryClient();
await qc.prefetchQuery({
queryKey: ["todos"],
queryFn: () => fetch("https://api/todos").then((r) => r.json()),
});
const state = dehydrate(qc);
return <Hydrated state={state} />;
}
// ํด๋ผ์ด์ธํธ: Hydrate๋ก ๋ณต์ ์์ฒญ ๋ฐฉ์ง
("use client");
import {
HydrationBoundary,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
export function Hydrated({ state }: { state: unknown }) {
const qc = new QueryClient();
return (
<QueryClientProvider client={qc}>
<HydrationBoundary state={state}>{/* children */}</HydrationBoundary>
</QueryClientProvider>
);
}
ํํ ์ค์ ์ฒดํฌ๋ฆฌ์คํธ
- ๋ถ์์ ํ
queryKey๋ก ๋งค ๋ ๋ ์ฌ์์ฒญ ๋ฐ์
staleTime๊ณผ gcTime ํผ๋(์ ์ ๋ vs ๋ฉ๋ชจ๋ฆฌ ์์กด)
- Mutation ํ ๊ด๋ จ ํค ๋ฌดํจํ ๋๋ฝ
- ๋๊ด์ ์
๋ฐ์ดํธ ์ ๋กค๋ฐฑ ์ปจํ
์คํธ ๋ฏธ๊ตฌํ
SWR๊ณผ์ ๊ฐ๋จ ๋น๊ต
- ๋ ๋ค ์บ์ฑ/๋ฆฌ๋ฐธ๋ฆฌ๋ฐ์ด์
์ ๊ณต. ๋๊ท๋ชจ ์ฑ์์ React Query๋ ์ฟผ๋ฆฌ/๋ฎคํ
์ด์
, ๋ฌดํ ์ฟผ๋ฆฌ, DevTools, SSR ํ์/์ํ ๋ฑ ๊ด๋ฆฌ ๊ธฐ๋ฅ์ด ๋ ํ๋ถํ๋ค. SWR์ API๊ฐ ๋จ์ํ๊ณ ๊ฐ๋ฒผ์ ๋น ๋ฅธ ๋์
์ ์ ๋ฆฌ.
๊ฒฐ๋ก
- React Query๋ ์๋ฒ ์ํ ๊ด๋ฆฌ์ ๋ณด์ผ๋ฌํ๋ ์ดํธ๋ฅผ ์ ๊ฑฐํ๊ณ ์์ธก ๊ฐ๋ฅํ UX๋ฅผ ๋ง๋ ๋ค. ํค ์ค๊ณ, ์ ์ ๋/๋ฌดํจํ ์ ๋ต, ๋๊ด์ ์
๋ฐ์ดํธ๋ง ์ ์ค๊ณํ๋ฉด ๋๋ค์ ๋ฐ์ดํฐ ํ๋ฆ์ด ๋จ์ํด์ง๋ค.