ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

Tanstack Query + Context API + Zustand๋กœ ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌํ•˜๊ธฐ 

 *Context API : ์ƒํƒœ๋ฅผ ์ „์—ญ์œผ๋กœ ๊ณต์œ 

 

4์ฃผ์ฐจ ์ˆ™์ œ

 

๐Ÿค” Tanstack Query + Zustand ์™œ ๊ฐ™์ด ์“ธ๊นŒ?

ํƒ ์Šคํƒ์€ ์„œ๋ฒ„ ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์บ์‹ฑํ•œ๋‹ค.

์ฃผ์Šคํƒ ์Šค๋Š” ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‘˜์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด, ์„œ๋ฒ„ ์ƒํƒœ๋ฅผ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๋กœ ๋™๊ธฐํ™”ํ•˜๋Š” ํŒจํ„ด์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

 

์ˆ™์ œ ์ˆ˜ํ–‰ ๋‹จ๊ณ„

 

1.  RQProvider.tsx ์—์„œ ์„œ๋ฒ„/๋ธŒ๋ผ์šฐ์ € ๊ฐ๊ฐ ์ƒˆ๋กœ์šด ์ฟผ๋ฆฌ ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ

๋”๋ณด๊ธฐ
"use client";

import {
  isServer,
  QueryClient,
  QueryClientProvider,
} from "@tanstack/react-query";

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000,
      },
    },
  });
}

let browserQueryClient: QueryClient | undefined = undefined;

// ์„œ๋ฒ„์™€ ๋ธŒ๋ผ์šฐ์ €๋กœ ๋‚˜๋ˆ„์–ด ๋ณ„๋„์˜ ์ฟผ๋ฆฌ ํด๋ผ์ด์–ธํŠธ ๋ถ€์—ฌ
function getQueryClient() {
  if (isServer) {
    return makeQueryClient();
  } else {
    if (!browserQueryClient) browserQueryClient = makeQueryClient();
    return browserQueryClient;
  }
}

export default function Providers({ children }: { children: React.ReactNode }) {
  const queryClient = getQueryClient();

  return (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
}

 

 

2. Root layout์— provider๋กœ ๊ฐ์‹ธ๊ธฐ

๋”๋ณด๊ธฐ
...

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <Providers>{children}</Providers>
      </body>
    </html>
  );

 

3. route.ts์—์„œ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ์ƒ์„ฑ

๋”๋ณด๊ธฐ
import { NextResponse } from "next/server";

export async function GET() {
  const count = { value: 0 };
  return NextResponse.json(count);
}

 

4. queryApi.ts์—์„œ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ๊ฒฝ๋กœ์—์„œ ๋ฐ์ดํ„ฐ ํŽ˜์นญ ํ•จ์ˆ˜ ์ƒ์„ฑ

๋”๋ณด๊ธฐ
export const getCounts = async (): Promise<number> => {
  const res = await fetch("/api/counts");
  const data = await res.json();

  return data;
};

 

5. page.tsx์—์„œ ํ”„๋ฆฌํŽ˜์น˜์ฟผ๋ฆฌ๋กœ ์ •์  html ๋งŒ๋“ค์–ด์„œ ์ฃผ์ž…(์ฟผ๋ฆฌํ•จ์ˆ˜ : ๋ฐ์ดํ„ฐ ํŽ˜์นญ ํ•จ์ˆ˜)

๋”๋ณด๊ธฐ
import {
  dehydrate,
  HydrationBoundary,
  QueryClient,
} from "@tanstack/react-query";
import Counter from "./_components/counter";
import { getCounts } from "./utils/queryApi";

export default function Home() {
  const queryClient = new QueryClient();

  // ํ”„๋ฆฌํŽ˜์น˜
  queryClient.prefetchQuery({
    queryKey: ["count"],
    queryFn: getCounts,
  });

  return (
    // ํ”„๋ฆฌํŽ˜์น˜๋ฅผ ์ •์  html๋กœ ๋งŒ๋“ค์–ด์„œ ์ฃผ์ž…
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Counter />
    </HydrationBoundary>
  );
}

 

6. countStore.ts์—์„œ Zustand ์Šคํ† ์–ด ์ƒ์„ฑ

๋”๋ณด๊ธฐ
import { create, StoreApi } from "zustand";

export type CountState = {
  count: number;
  increment: () => void;
  decrement: () => void;
};

export const createCounterStore = create<CountState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

 

๐Ÿค”TanstackProvider๋Š” root layout์—์„œ ๊ฐ์ŒŒ๋Š”๋ฐ, Zustand๋Š”?

์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ์—” Zustand Provider๊ฐ€ ํ•„์š” ์—†๋‹ค.

  • Provider ์—†์ด ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ
  • ํ•˜์ง€๋งŒ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์™€ ์—ฐ๋™ํ•ด์•ผ ํ•˜๊ฑฐ๋‚˜, ํŠน์ • ์ปดํฌ๋„ŒํŠธ๋งŒ ๋…๋ฆฝ์ ์ธ Zustand ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์•ผ ํ•  ๊ฒฝ์šฐ์—๋Š” Provider๊ฐ€ ํ•„์š”ํ•จ.
  • SSR์—์„œ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์ฃผ์ž…ํ•ด์•ผ ํ•  ๊ฒฝ์šฐ์—๋„ Provider๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Œ.

=>  ๊ตณ์ด ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์™€ ์—ฐ๊ฒฐํ•  ํ•„์š”๊ฐ€ ์—†์–ด์„œ Zustand Provider ์ ์šฉ ์•ˆ ํ•จ

 

๐Ÿค” counter.tsx์—์„œ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•œ useQuery ๊ฐ’์„ ์‚ฌ์šฉํ• ์ง€, Zustand Store๋กœ ๊ฐ’์„ ๊ด€๋ฆฌํ• ์ง€

์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ• ์ง€ ๋ง์ง€ ๊ฒฐ์ •ํ•˜๊ธฐ

  • ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์„ค์ •ํ•œ ์ดˆ๊ธฐ๊ฐ’ (value : 0)์„ useQuery์—์„œ ์บ์‹ฑ ์ค‘
  • ์„œ๋ฒ„์—์„œ ์บ์‹ฑ ์ค‘์ธ ๊ฐ’์„ ์‚ฌ์šฉ(๊ทธ ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™”)ํ•˜๋ ค๋ฉด ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ useQuery์™€ useEffect ์‚ฌ์šฉ
  • Store์—์„œ ์„ค์ •ํ•œ ์ดˆ๊ธฐ๊ฐ’์„ ์‚ฌ์šฉํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ ๋‚ด๋ถ€์—์„œ๋งŒ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋ ค๋ฉด Zustand๋งŒ ์‚ฌ์šฉ

=>  ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํด๋ผ์ด์–ธํŠธ์—์„œ๋งŒ ์ƒํƒœ ๊ด€๋ฆฌ ์œ„ํ•ด Zustand๋งŒ ์‚ฌ์šฉ

 

 

โœ”๏ธ Zustand Provider ์‚ฌ์šฉ ์ด์œ 

  • ์ปจํ…์ŠคํŠธ(Context API)์™€ ํ•จ๊ป˜ Zustand๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”
  • Provider ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ Zustand ์ƒํƒœ๋ฅผ Context๋กœ ๊ฐ์‹ธ๋Š” ๋ฐฉ๋ฒ• ์‚ฌ์šฉ
๋”๋ณด๊ธฐ
// CountProvider.tsx

// ์„œ๋ฒ„์—์„œ Zustand ์ƒํƒœ๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ Provider ํ•„์š”

"use client";

import { CountState, createCounterStore } from "@/countStore";
import { createContext, ReactNode, useRef } from "react";
import { StoreApi } from "zustand";

// ์ดˆ๊ธฐ๊ฐ’ null : ์™ธ๋ถ€์—์„œ ์‚ฌ์šฉ ์‹œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ
export const CountContext = createContext<StoreApi<CountState> | null>(null);

export type CounterProviderProps = {
  children: ReactNode; // Provider ๋‚ด๋ถ€์— ๋ชจ๋“  JSX ์š”์†Œ ๊ฐ€๋Šฅ
};

export function CountProvider({ children }: CounterProviderProps) {
  // useRef : Zustand ์ƒํƒœ(storeRef.current) ํ•œ ๋ฒˆ๋งŒ ์ƒ์„ฑ(๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง ๋ฐฉ์ง€)
  const storeRef = useRef<StoreApi<CountState> | null>(null);
  // ์Šคํ† ์–ด๊ฐ€ ์—†์„ ๋•Œ ์ƒˆ๋กญ๊ฒŒ ์ƒ์„ฑ(์ฒ˜์Œ ๋งˆ์šดํŠธ ์‹œ) / ์ด๋ฏธ ์žˆ๋‹ค๋ฉด ๊ธฐ์กด ์ƒํƒœ ์œ ์ง€
  if (!storeRef.current) {
    storeRef.current = createCounterStore();
  }
  return (
    // ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ ํ˜„์žฌ Zustand ์ƒํƒœ ์ „๋‹ฌ
    <CountContext.Provider value={storeRef.current}>
      {children}
    </CountContext.Provider>
  );
}

 

  • createCounterStore: Zustand ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜ (countStore.ts์—์„œ ์ •์˜)
  • CountState: Zustand ์ƒํƒœ์˜ ํƒ€์ž… ์ •์˜ (countStore.ts์—์„œ ๊ฐ€์ ธ์˜ด)
  • createContext: React์˜ Context API๋กœ ์ƒํƒœ๋ฅผ ์ „์—ญ์œผ๋กœ ๊ณต์œ ํ•  ๋•Œ ์‚ฌ์šฉ
  • ReactNode: children์˜ ํƒ€์ž…์œผ๋กœ ์‚ฌ์šฉ (Provider ์•ˆ์— ์–ด๋–ค JSX ์š”์†Œ๋“  ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •)
  • useRef: React์˜ ์ฐธ์กฐ(ref) ํ›…์œผ๋กœ ํ•œ ๋ฒˆ๋งŒ Zustand ์Šคํ† ์–ด๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ๋ณด์žฅ
  • StoreApi: Zustand์˜ create ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ์Šคํ† ์–ด ๊ฐ์ฒด์˜ ํƒ€์ž…

 

 

 

๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
TAG
more
ยซ   2026/03   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
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
๊ธ€ ๋ณด๊ด€ํ•จ