React에서 Context API는 전역 상태나 공통 데이터를 전달할 때 자주 사용됩니다. 하지만 TypeScript 없이 Context를 사용하면 값 구조를 추측해야 하고, Provider 누락·타입 불일치로 인한 런타임 오류가 쉽게 발생합니다.
Context API가 필요한 상황
Context는 다음과 같은 경우에 적합합니다.
- 로그인 사용자 정보
- 테마(dark / light)
- 언어(i18n)
- 전역 설정 값
반대로, 자주 변경되거나 복잡한 비즈니스 상태에는 React Query, Zustand 같은 상태 관리 라이브러리가 더 적합합니다.
Context에 타입이 반드시 필요한 이유
타입 없이 Context를 만들면 다음과 같은 문제가 발생합니다.
- Context 값 구조를 IDE에서 알 수 없음
- Provider를 빼먹어도 컴파일 단계에서 감지 불가
- value 변경 시 사용처 전체를 수동으로 확인
TypeScript를 사용하면 Context는 “전역 상태의 타입 계약”이 됩니다.
Context 타입 먼저 정의하기
가장 중요한 원칙은 Context 생성 전에 타입을 먼저 정의하는 것입니다.
type AuthContextValue = {
user: User | null;
login: (user: User) => void;
logout: () => void;
};
Context의 역할과 책임이 이 타입 하나에 모두 드러납니다.
createContext의 올바른 사용 패턴
Context 생성 시 가장 흔한 실수는 임의의 기본값을 넣는 것입니다.
// X 권장하지 않음
createContext({} as AuthContextValue);
이 방식은 타입 안전성을 깨뜨립니다. 실무에서는 undefined 기반 Context가 가장 안전합니다.
import { createContext } from "react";
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
이렇게 하면 Provider 없이 사용했을 때 즉시 오류를 발생시킬 수 있습니다.
Provider 컴포넌트 구현
import { useState } from "react";
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const value: AuthContextValue = {
user,
login: setUser,
logout: () => setUser(null),
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
핵심 포인트:
- Context value는 명시적으로 타입 지정
- State와 Context 타입 분리
- children은
React.ReactNode
커스텀 훅으로 안전하게 사용하기
Context를 직접 useContext로 사용하는 것은 권장되지 않습니다. 대신 커스텀 훅으로 감싸는 것이 실무 표준입니다.
import { useContext } from "react";
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) {
throw new Error("useAuth must be used within AuthProvider");
}
return ctx;
}
이 훅 하나로 다음이 보장됩니다.
- Provider 누락 즉시 오류 발생
- 반환 타입은 항상
AuthContextValue - 사용부 코드 단순화
Context 사용 예시
function Profile() {
const { user, logout } = useAuth();
if (!user) {
return <div>Not logged in</div>;
}
return (
<div>
<span>{user.name}</span>
<button onClick={logout}>Logout</button>
</div>
);
}
Context 값이 명확한 타입으로 보장되기 때문에 불필요한 null 체크나 타입 단언이 사라집니다.
여러 Context를 다룰 때의 패턴
Context가 많아질수록 다음 원칙이 중요합니다.
- Context 하나당 책임 하나
- 전역 상태와 UI 상태 분리
- Provider 중첩은 App 레벨에서 관리
<AuthProvider>
<ThemeProvider>
<App />
</ThemeProvider>
</AuthProvider>
이 구조는 가독성과 확장성 면에서 가장 안정적입니다.
실무에서 자주 하는 실수
- createContext 기본값에 더미 객체 사용
- Context 타입과 실제 value 구조 불일치
- Provider 없이 useContext 직접 사용
- Context에 너무 많은 책임을 부여
대부분 “타입을 중심으로 설계하지 않았기” 때문에 발생합니다.
실무 권장 정리
- Context 타입부터 정의
- createContext는
undefined기반 - 반드시 커스텀 훅으로 감싸서 사용
- Context는 전역 설정/상태에만 사용
Context API와 TypeScript를 함께 사용하면 전역 상태는 더 이상 불안한 공유 변수가 아니라 명확한 타입 계약을 가진 설계 요소가 됩니다.
- Context = 전역 계약
- 타입이 설계를 이끈다
- 커스텀 훅은 필수
