본문 바로가기
C.W.K.
Stream
Lesson 05 of 07 · published

useContext로 트리 아래에 값 공유하기

~14 min · usecontext, context, providers

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
Context는 여러 단계의 prop 전달 없이 트리 아래에 값을 공유해. 무엇을 넣고 어떻게 소비할지 계약부터 정해 보자.

Context를 만드는 세 단계

createContext(defaultValue)로 Context 객체를 만들고, Provider의 value에 실제 값을 넣어. 자식 컴포넌트는 useContext로 가장 가까운 Provider 값을 읽어. React 19에서는 Context 객체 자체를 Provider로 렌더링하는 새 문법도 사용할 수 있어.

안전한 소비자 훅을 만들어

Provider 밖에서 읽으면 조용히 잘못된 기본값을 쓰는 대신, 기본값을 null로 두고 useTheme 같은 전용 훅에서 빠르게 오류를 던지는 편이 안전해. 그러면 사용하는 컴포넌트가 매번 null 검사를 반복하지 않아도 돼.

Provider 값이 바뀌면 소비자도 다시 렌더링돼

Value 객체를 렌더링마다 새로 만들면 실제 내용이 같아도 모든 소비자 컴포넌트가 다시 렌더링될 수 있어. 필요한 경우 값을 메모이제이션하고, theme·auth·locale처럼 변경 주기가 다른 관심사는 Context를 나눠.

Context에 넣지 않을 값

한두 컴포넌트만 쓰는 로컬 상태나 자주 바뀌는 큰 데이터 묶음까지 모두 Context에 넣으면 변경 범위가 너무 넓어져. 가까운 컴포넌트에는 prop이 더 단순하고, 서버 데이터 캐시에는 TanStack Query 같은 전용 도구가 더 잘 맞을 수 있어.

Context는 값을 공유하는 경로지 상태 관리 전략 전체가 아니야. 값의 소유자와 업데이트 규칙은 별도로 분명하게 정해.

Prop drilling을 줄이는 도구야

중간 컴포넌트가 사용하지도 않는 theme이나 사용자 값을 아래로 전달하기 위해 prop만 계속 넘기는 상황을 prop drilling이라고 해. Context는 가장 가까운 Provider에서 필요한 자식이 직접 값을 읽게 해 이 전달 사슬을 없애.

React 19의 Provider 문법

이전에는 <ThemeContext.Provider value={value}>라고 썼지만 React 19에서는 <ThemeContext value={value}>처럼 Context 객체 자체를 Provider로 사용할 수 있어. 기존 .Provider 문법도 계속 동작해.

Compound component에도 Context가 쓰여

Tabs가 active tab을 소유하고 Tab 자식이 선택 여부와 변경 함수를 읽는 구조처럼, 서로를 알아야 하는 compound component의 좁은 내부 계약에도 잘 맞아. 이 경우 Provider 범위를 해당 컴포넌트 트리 안으로 제한해.

더 정교한 store가 필요한 경계

자주 바뀌는 값이 많고 소비자마다 일부만 구독해야 한다면 Context 하나는 너무 넓게 다시 렌더링할 수 있어. Zustand, Jotai, Redux Toolkit 같은 store는 selector와 업데이트 규칙을 제공해. 서버 데이터는 TanStack Query가 캐시와 재검증까지 맡는 편이 자연스러워.

Code

Theme Context 값을 안전하게 읽기·tsx
import { createContext, useContext, useMemo, useState } from "react";

type Theme = "light" | "dark";
type ThemeCtxValue = { theme: Theme; setTheme: (t: Theme) => void };

// 기본값이 null이므로 소비자는 Provider 안에 있어야 해.
const ThemeCtx = createContext<ThemeCtxValue | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>("dark");
  // 객체 참조가 렌더링 사이에서 유지되도록 값을 메모이제이션해.
  const value = useMemo(() => ({ theme, setTheme }), [theme]);
  return <ThemeCtx value={value}>{children}</ThemeCtx>;
}

// 커스텀 훅이 Context 읽기를 맡고 Provider가 빠지면 오류를 내.
export function useTheme(): ThemeCtxValue {
  const ctx = useContext(ThemeCtx);
  if (!ctx) throw new Error("useTheme은 <ThemeProvider> 안에서 사용해야 해");
  return ctx;
}

// 호출자는 useContext를 직접 다루지 않고 안전한 커스텀 훅을 사용해.
function ThemeToggle() {
  const { theme, setTheme } = useTheme();
  return (
    <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
      Switch to {theme === "dark" ? "light" : "dark"}
    </button>
  );
}
useMemo 함정·tsx
// 잘못된 예: value 객체를 렌더링마다 새로 만들면 모든 소비자가 다시 렌더링돼.
function BadProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>("dark");
  return (
    <ThemeCtx value={{ theme, setTheme }}>
      {children}
    </ThemeCtx>
  );
}

// 올바른 예: theme이 바뀔 때만 새 value 객체를 만들어 참조를 유지해.
function GoodProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>("dark");
  const value = useMemo(() => ({ theme, setTheme }), [theme]);
  return <ThemeCtx value={value}>{children}</ThemeCtx>;
}

// (React Compiler, Track 7, Lesson 4가 컴파일러 활성화된 프로젝트에서
// 이 메모이제이션을 자동으로 적용할 수 있어.)

External links

Exercise

locale: 'en' | 'ko'와 번역 함수 t(key)를 제공하는 LocaleProvider와 안전한 useLocale 훅을 만들어. 서로 관련 없는 두 컴포넌트가 같은 번역 출처를 읽는지 확인하고, Provider를 제거했을 때 훅이 이해하기 쉬운 오류를 내게 해. 번역이 없는 key는 key 자체를 보여 주고, locale이 그대로일 때 value 참조와 소비자 렌더링이 불필요하게 바뀌지 않는지도 측정해.
Hint
Provider 안에서 value를 useMemo로 유지하고 translations[locale]?.[key] ?? key를 사용해. Provider 있음, Provider 없음, 빠진 번역, 같은 locale 재렌더링 네 경우를 확인해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.