ReactReact 기초 · 7기초

Context API — 전역 상태 공유

ReactContextuseContext전역상태PropDrilling

Prop Drilling 문제

flowchart TB
    APP["App\nuser={user}"]
    LAYOUT["Layout\nuser={user}"]
    SIDEBAR["Sidebar\nuser={user}"]
    AVATAR["Avatar\nuser={user}"]

    APP --> LAYOUT --> SIDEBAR --> AVATAR

    NOTE["Avatar만 user가 필요한데\n중간 컴포넌트를 모두 거쳐야 함"]

Context를 쓰면 중간 단계 없이 직접 전달할 수 있습니다.


Context 만들기

// src/contexts/AuthContext.tsx
import { createContext, useContext, useState, ReactNode } from "react";

interface User {
    id: string;
    name: string;
    email: string;
    role: "admin" | "user";
}

interface AuthContextValue {
    user: User | null;
    login: (email: string, password: string) => Promise<void>;
    logout: () => void;
    isLoading: boolean;
}

const AuthContext = createContext<AuthContextValue | null>(null);

// 커스텀 훅으로 컨텍스트 사용 간소화
export function useAuth() {
    const context = useContext(AuthContext);
    if (!context) throw new Error("useAuth는 AuthProvider 안에서 사용해야 합니다.");
    return context;
}

export function AuthProvider({ children }: { children: ReactNode }) {
    const [user, setUser] = useState<User | null>(null);
    const [isLoading, setIsLoading] = useState(false);

    async function login(email: string, password: string) {
        setIsLoading(true);
        try {
            const response = await fetch("/api/auth/login", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ email, password }),
            });
            const data = await response.json();
            setUser(data.user);
        } finally {
            setIsLoading(false);
        }
    }

    function logout() {
        setUser(null);
    }

    return (
        <AuthContext.Provider value={{ user, login, logout, isLoading }}>
            {children}
        </AuthContext.Provider>
    );
}

Provider로 감싸기

// src/main.tsx
import { AuthProvider } from "./contexts/AuthContext";

createRoot(document.getElementById("root")!).render(
    <StrictMode>
        <AuthProvider>
            <App />
        </AuthProvider>
    </StrictMode>
);

어디서나 사용

// 어떤 깊이의 컴포넌트에서든
function UserAvatar() {
    const { user } = useAuth();

    if (!user) return null;
    return <img src={`/avatars/${user.id}`} alt={user.name} />;
}

function LogoutButton() {
    const { logout } = useAuth();
    return <button onClick={logout}>로그아웃</button>;
}

function AdminPanel() {
    const { user } = useAuth();

    if (user?.role !== "admin") return <p>접근 권한이 없습니다.</p>;
    return <div>관리자 패널</div>;
}

테마 Context 예시

// src/contexts/ThemeContext.tsx
type Theme = "light" | "dark";

interface ThemeContextValue {
    theme: Theme;
    toggleTheme: () => void;
}

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

export function useTheme() {
    return useContext(ThemeContext);
}

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

    useEffect(() => {
        document.documentElement.setAttribute("data-theme", theme);
    }, [theme]);

    const toggleTheme = () => setTheme(t => t === "light" ? "dark" : "light");

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

// 사용
function DarkModeToggle() {
    const { theme, toggleTheme } = useTheme();
    return (
        <button onClick={toggleTheme}>
            {theme === "light" ? "🌙 다크모드" : "☀️ 라이트모드"}
        </button>
    );
}

Context 성능 고려

// 분리되지 않은 Context — 일부만 바뀌어도 전체 구독자 리렌더링
const UserContext = createContext({ user, settings, notifications });

// ✅ 분리된 Context — 각각 독립 업데이트
const UserContext = createContext(user);
const SettingsContext = createContext(settings);
const NotificationsContext = createContext(notifications);

정리

flowchart LR
    PROVIDER["Context.Provider\n값 제공"]
    CONSUMER1["컴포넌트 A\nuseContext()"]
    CONSUMER2["컴포넌트 B\nuseContext()"]
    CONSUMER3["깊은 컴포넌트\nuseContext()"]

    PROVIDER --> CONSUMER1
    PROVIDER --> CONSUMER2
    PROVIDER --> CONSUMER3
개념설명
createContextContext 생성
Provider값을 하위 트리에 제공
useContextContext 값 소비
커스텀 훅useAuth, useTheme 등 사용성 개선

다음 편에서는 커스텀 훅 — 로직을 재사용 가능한 훅으로 추출하는 방법을 배웁니다.

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.