C.W.K.
Stream
Lesson 05 of 07 · published

useContext — 트리 전반에 state 공유

~14 min · usecontext, context, providers

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
Context 는 React 의 prop-drilling 탈출구. 진짜로 tree-wide 인 것 (theme, auth, locale) 에 써 — 세 단계 통과시키기 귀찮은 모든 것엔 아니야.

세 부분

  1. createContext(defaultValue) — Context object 반환 (React 19 에선 Context 자체가 Provider, .Provider 필요 없음).
  2. Provider — subtree 감싸고 현재 값 공급: <MyCtx value={...}>.
  3. useContext(MyCtx) — 어떤 descendant 에서도 가장 가까운 provider 의 값 읽음.

Default 값 (createContext 의 인자) 은 consumer 위에 Provider 가 없을 때 보는 값. Nullable 디폴트에 써; 많은 앱이 Provider 빠지면 throw 하는 hook 으로 consumer 감쌈 (코드 블록 참조).

React 19 의 Provider 로서의 <Context>

React 19 전엔 <MyCtx.Provider value={...}> 였어. 19 에선 Context object 를 Provider 로 직접 사용 가능 — <MyCtx value={...}>. 둘 다 동작; 새 문법이 더 짧고 호출자가 값을 생각하는 방식에 맞아.

Re-render

Provider 의 value 가 (reference equality 로) 바뀌면 모든 consumer 가 re-render. Render 에서 만든 객체 — value={{ theme, setTheme }} — 가 value 면 매 render 마다 바뀌고 모든 consumer 가 re-render. useMemo 로 객체 memoize, 또는 React Compiler (Track 7) 가 대신 해주길 신뢰.

Context 손이 갈 때

  • Theme (light/dark, accent 색).
  • Auth (현재 사용자, login/logout 함수).
  • Locale + translation.
  • Compound-component state (Track 2 lesson 3 에서 본 거).
  • 진짜로 tree-wide 가 필요한 앱 전역 설정.

아닐 때

  • Server state (페치 데이터) — query 라이브러리 (TanStack Query) 또는 본인 커스텀 hook.
  • 대부분의 consumer 가 신경 안 쓰는 자주 바뀌는 값 — Context 가 다 re-render 시킴.
  • Form state — 로컬 또는 Zustand store.
Context 는 state-management 라이브러리 아냐. Prop-bypass 메커니즘. 정교한 업데이트 패턴 가진 cross-component state 필요하면 Zustand, Jotai, Redux Toolkit. Context 의 일은 전달 — state 아키텍처 아님.

Code

Theme context, safe-consumer 패턴·tsx
import { createContext, useContext, useMemo, useState } from "react";

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

// Default 가 null — consumer 가 Provider 안에 있어야.
const ThemeCtx = createContext<ThemeCtxValue | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>("dark");
  // 객체 reference 가 render 간 안정적이도록 memoize.
  const value = useMemo(() => ({ theme, setTheme }), [theme]);
  return <ThemeCtx value={value}>{children}</ThemeCtx>;
}

// 커스텀 hook 이 캐논 consumer — Provider 빠지면 throw.
export function useTheme(): ThemeCtxValue {
  const ctx = useContext(ThemeCtx);
  if (!ctx) throw new Error("useTheme 은 <ThemeProvider> 안에서 사용");
  return ctx;
}

// 호출자 — 명시적 useContext 없음, 빠진 Provider 리스크 없음.
function ThemeToggle() {
  const { theme, setTheme } = useTheme();
  return (
    <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
      Switch to {theme === "dark" ? "light" : "dark"}
    </button>
  );
}
useMemo 함정·tsx
// WRONG — value 객체가 매 render 마다 fresh, 모든 consumer 가 re-render.
function BadProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>("dark");
  return (
    <ThemeCtx value={{ theme, setTheme }}>
      {children}
    </ThemeCtx>
  );
}

// RIGHT — value memoize, reference 가 theme 바뀔 때까지 안정.
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, 가 컴파일러 활성화된 프로젝트에서
// 자동으로 이걸 memoize 함.)

External links

Exercise

{ locale: 'en' | 'ko'; t: (key: string) => string } 잡는 <LocaleProvider> 와 safe-consumer 패턴 가진 useLocale() hook 빌드. 작은 translation 객체를 Provider 에 prop 으로 전달. 무관한 컴포넌트 둘에서 사용해서 둘 다 같은 출처에서 끌어오는지 확인. Provider 제거해서 커스텀 hook 이 명확한 에러 throw 하는지 확인.
Hint
Provider 안: const value = useMemo(() => ({ locale, t: (key) => translations[locale]?.[key] ?? key }), [locale, translations]);. Fallback ?? key 가 번역 없으면 key 자체 보여줌.

Progress

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

댓글 0

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

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