끄적이는 개발노트

Next.js 15 - 전역 상태 Context API 본문

JavaScript/Next.js

Next.js 15 - 전역 상태 Context API

크런키스틱 2025. 4. 24. 20:19
728x90

React에서 전역 상태 관리를 하는 방법에는 Redux, Recoil, Zustand 등과 같이 다양하고 편리한 라이브러리가 많다. 이 중에서 본인은 Redux와 Recoil만 사용해봤는데 정작 React에서 제공하는 내장 도구 Context API를 사용해본 적이 없었다. 이번에 사용법을 익힐 기회가 생겨서 정리한다.

 

 

1. Context API

Context API는 React에서 전역 상태를 관리하기 위한 내장 도구이다. 컴포넌트 트리 전체에 데이터를 전달할 수 있으며, props drilling을 피하기 위해 사용된다.

다만, 공유하는 context가 변경되면 사용하는 모든 컴포넌트는 리렌더링되기 때문에, 상태의 목적과 용도에 따라 context를 잘나눠서 provider를 통해 필요한 컴포넌트에만 접근해야 한다. 그렇다고 리렌더링을 최소화하기 위해 매번 나눠서 사용하다보면 Provider가 엄청나게 늘어나는 것을 경험할 수 있다.

따라서, Context API는 복잡한 구조의 전역 상태보다는 간단한 전역 상태를 관리할 때 적합하다.

 

  • context : 전역 상태를 생성
  • provider : 자식 컴포넌트에게 전역 상태를 제공하는 컴포넌트
  • consumer : 전역 상태를 사용

 

 

2. 코드

src 폴더 아래에 context 폴더를 만들고 아래에 context 파일을 생성했다. (src/context/themeContext.tsx)

 

context 생성 (createContext)

// themeContext.tsx

interface ThemeContext {
  theme: "light" | "dark";
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContext>({
  theme: "light",
  toggleTheme: () => {},
});

 

초기 값은 보통 null 이나 원하는 기본값을 넣어준다.

 

 

provider 생성

//themeContext.tsx

interface ThemeProvider {
  children: ReactNode;
}

export const ThemeProvider = ({ children }: ThemeProvider) => {
  const [theme, setTheme] = useState<"light" | "dark">("light");

  const toggleTheme = () => {
    setTheme(theme === "light" ? "dark" : "light");
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

Context.Provider를 통해 children을 감싸주고 value prop에 전역 상태로 관리할 state를 전달한다.

해당 provider에서 state에 대한 업데이트가 처리되고 결과값만 사용할 예정이라면 state만 전달해도 문제없다.

 

 

consumer 사용 (useContext)

export const useTheme = () => useContext(ThemeContext);

 

 

themeContext.tsx 전체 코드

Context API의 createContext는 클라이언트 컴포넌트에서 동작하기 때문에 "use clinet" 지시문을 추가한다.

// themeContext.tsx

"use client";

import {
  createContext,
  ReactNode,
  useContext,
  useState,
} from "react";

interface ThemeProvider {
  children: ReactNode;
}

interface ThemeContext {
  theme: "light" | "dark";
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContext>({
  theme: "light",
  toggleTheme: () => {},
});

export const ThemeProvider = ({ children }: ThemeProvider) => {
  const [theme, setTheme] = useState<"light" | "dark">("light");

  const toggleTheme = () => {
    setTheme(theme === "light" ? "dark" : "light");
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

export const useTheme = () => useContext(ThemeContext);

 

 

3. layout.tsx에 적용

app root에 존재하는 layout.tsx 파일의 children을 Provider로 감싸준다.

// layout.tsx

...
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={gmarket.className}>
        <ThemeProvider>
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

 

 

4. context 사용

상태가 전역적으로 바뀌고 있는지를 확인하기 위해 페이지를 두개로 나눠서 테스트한다.

// app/page.tsx

"use client";

import { useTheme } from "@/context/themeContext";
import Link from "next/link";

export default function Home() {
  const { theme } = useTheme();

  return (
    <div className="w-full h-full flex flex-col justify-center items-center gap-4 bg-white">
      <Link
        className="p-4 border border-gray-400 rounded-lg"
        href="/test"
        scroll={false}
      >
        테스트 페이지
      </Link>
      <p>{theme}</p>
    </div>
  );
}
// app/test/page.tsx

"use client";

import { useTheme } from "@/context/themeContext";
import Link from "next/link";

const TestPage = () => {
  const { toggleTheme } = useTheme();

  return (
    <div className="w-full h-full flex flex-col justify-center items-center gap-4">
      <p className="text-3xl text-black">테스트입니다.</p>
      <Link href="/" className="p-4 ring ring-gray-400 rounded-lg">
        홈으로
      </Link>
      <button
        type="button"
        className="p-4 ring ring-gray-400 rounded-lg"
        onClick={toggleTheme}
      >
        테마변경
      </button>
    </div>
  );
};

export default TestPage;

 

 

5. 실행

실행해보면 아래와 같이 theme state가 잘 변경되는 것을 확인할 수 있다.

물론, 여기서 지정한 theme state는 예제를 위한 state기 때문에 명칭만 theme일 뿐 다른 기능을 위한 코드는 없다. 실제 다크모드를 적용하는데에는 TailwindCSS와 localStorage를 활용할 필요가 있다. Next.js에서는 next-themes라는 근사한 라이브러리가 있기 때문에 더 수월하게 적용 가능하다.

 

728x90