Skip to Content
WebTypeScriptTypeScript 실무07. Context 타이핑

이번 편의 결과물: ThemeContext.tsx 전환이 완료되고, Provider 밖에서 useTheme을 호출하면 명확한 에러가 나는 것을 그대로 확인합니다. · 다루는 개념: createContext<T | null> 패턴, Provider 밖 사용을 막는 커스텀 훅, Context 값 타입 설계

bookshelfFilterContext를 09편(react_3, Zustand 도입)에서 걷어내고 ThemeContext만 남겼습니다. 06편까지 훅을 전환했으니, 이 편은 bookshelf-ts에 남은 마지막 Context 하나를 전환합니다.

이 편에서 만드는 파일

bookshelf-ts/src/ └── context/ └── ThemeContext.tsx ~ .jsx → .tsx 전환 (값 타입 설계, null 가드)

개념 정리

createContext의 타입 매개변수

createContext(초기값)은 초기값의 타입으로부터 Context가 담을 값의 타입을 추론합니다. ThemeContext처럼 실제 값이 Provider를 통해서만 채워지고 그 전에는 존재하지 않는 경우, 초기값을 null로 두면서도 실제 사용 시점에는 null이 아니라고 보장하고 싶은 경우가 많습니다. 이때 타입 매개변수를 명시적으로 유니온으로 씁니다.

const ThemeContext = createContext<ThemeContextValue | null>(null)

이렇게 하면 useContext(ThemeContext)의 반환 타입이 ThemeContextValue | null이 되어, 값을 쓰기 전에 null 여부를 반드시 확인하게 만듭니다.

Provider 밖 사용을 막는 커스텀 훅

useContext를 직접 노출하는 대신, 그 호출을 감싸고 null이면 에러를 던지는 훅을 만듭니다.

조각역할
createContext<T | null>(null)값이 없을 수 있다는 것을 타입으로 표현
useContext(Context)T | null 반환
if (context === null) throw ...런타임에 즉시 실패시켜 실수를 조기에 드러냄
함수의 반환 타입을 T로 명시이 지점을 통과하면 더 이상 null이 아니라고 타입도 확정

가드를 통과한 뒤 함수의 반환 타입을 T(null 제외)로 선언하면, useTheme()을 호출하는 컴포넌트 쪽에서는 null 검사를 다시 할 필요가 없습니다. 타입 좁히기가 훅 안에서 끝나고, 사용하는 쪽은 항상 값이 있다고 믿고 코드를 쓸 수 있습니다.

실습

1. ThemeContext를 tsx로 전환

// src/context/ThemeContext.tsx import { createContext, useContext, useState, type ReactNode } from 'react' export type Theme = 'light' | 'dark' interface ThemeContextValue { theme: Theme toggleTheme: () => void } const ThemeContext = createContext<ThemeContextValue | null>(null) interface ThemeProviderProps { children: ReactNode } export function ThemeProvider({ children }: ThemeProviderProps) { const [theme, setTheme] = useState<Theme>('light') function toggleTheme() { setTheme((prev) => (prev === 'light' ? 'dark' : 'light')) } const value: ThemeContextValue = { theme, toggleTheme } return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider> } export function useTheme(): ThemeContextValue { const context = useContext(ThemeContext) if (context === null) { throw new Error('useTheme은 ThemeProvider 내부에서만 사용합니다.') } return context }

런타임 로직은 원래 .jsx 코드와 완전히 같습니다. 바뀐 것은 Theme 타입, ThemeContextValue 인터페이스, ThemeProviderProps, 그리고 useTheme의 반환 타입 선언뿐입니다. themeuseState<Theme>('light')로 선언했으므로, setTheme'light'·'dark' 이외의 문자열을 넘기면 그 자리에서 오류가 납니다.

2. main.jsx의 import 확장자 확인

Header.tsx(04편)는 처음부터 확장자 없이 from '../context/ThemeContext'로 가져왔으므로 손댈 필요가 없습니다. 반면 아직 자바스크립트인 main.jsxfrom './context/ThemeContext.jsx'처럼 확장자를 명시해 가져오고 있었다면, 파일이 .tsx로 바뀐 지금 이 한 줄만 맞춰 둡니다.

// src/main.jsx (import 줄만 수정) import { ThemeProvider } from './context/ThemeContext.tsx'

나머지 코드는 그대로 둡니다. main.jsx는 아직 자바스크립트 파일이지만, .tsx 모듈을 가져다 쓰는 데는 문제가 없습니다.

3. 실행

npm run dev

4. 확인

  • “다크 모드” 버튼을 누르면 이전과 동일하게 텍스트가 “라이트 모드”로 바뀝니다.
  • VS Code에서 setTheme('blue')처럼 Theme에 없는 값을 넣어 보면 빨간 밑줄이 뜹니다. 확인 후 되돌립니다.
  • useTheme 함수 정의를 Ctrl(Cmd)+클릭하면 반환 타입이 ThemeContextValue로 표시되고, null 가능성이 사라져 있는 것을 확인합니다.
  • 잠깐 App 트리 밖(예: main.jsx에서 ThemeProvider로 감싸는 줄을 주석 처리)에서 렌더링을 시도해 콘솔에 “useTheme은 ThemeProvider 내부에서만 사용합니다.” 에러가 그대로 뜨는지 확인한 뒤 되돌립니다.

직접 해보기

ThemeContexttheme 저장 방식을 06편에서 만든 useLocalStorage<Theme>로 바꿔 보세요. 새로고침해도 테마가 유지되는지 확인합니다.

정답 보기

// src/context/ThemeContext.tsx (일부) import { useLocalStorage } from '../hooks/useLocalStorage' export function ThemeProvider({ children }: ThemeProviderProps) { const [theme, setTheme] = useLocalStorage<Theme>('bookshelf-theme', 'light') function toggleTheme() { setTheme((prev) => (prev === 'light' ? 'dark' : 'light')) } const value: ThemeContextValue = { theme, toggleTheme } return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider> }

useLocalStorage<Theme>로 타입 인자를 명시하면, 저장된 값이 손상되어 initialValue로 복구될 때도 그 값이 Theme('light' \| 'dark')이라는 것이 타입으로 보장됩니다.

자주 하는 실수

증상원인고치는 법
useTheme() 호출부에서 themeThemeContextValue | null로 보임useTheme의 반환 타입을 명시하지 않아 가드 이후에도 null이 안 좁혀짐useTheme(): ThemeContextValue처럼 반환 타입을 명시한다
Header.tsx에서 모듈을 찾을 수 없다는 오류ThemeContext.jsx import 경로를 .tsx로 안 바꿈확장자가 바뀐 모든 import 경로를 함께 수정한다
createContext(null)만 쓰고 타입 인자를 생략해 theme 접근 시 오류타입 매개변수 없이 createContext(null)을 쓰면 타입이 null 하나로 고정됨createContext<ThemeContextValue | null>(null)처럼 유니온으로 명시한다

확인 문제

문제 14지선다
ThemeContext를 createContext에서 null 대신 undefined로 초기화해도 되는가?
문제 24지선다
useTheme 함수의 반환 타입을 ThemeContextValue로 명시적으로 선언하는 효과는?
문제 34지선다
useState 호출 시 Theme을 타입 인자로 명시했을 때 얻는 효과는?

참고 자료

Last updated on