React Context API로 전역 상태 관리하기
Context가 잘 맞는 상태와 아닌 상태
Context는 “전역 상태”라는 표현 때문에 모든 상태 관리 문제의 답처럼 보이지만 실제로는 적합한 영역이 꽤 명확합니다.
잘 맞는 경우:
- 테마, 로케일, 사용자 세션 정보
- 화면 전반에서 반복적으로 참조하는 설정값
- 변경 빈도가 낮고 소비자가 넓게 퍼진 데이터
잘 맞지 않는 경우:
- 자주 바뀌는 폼 입력 상태
- 서버에서 가져오는 목록과 캐시 데이터
- 복잡한 업데이트 규칙이 많은 대규모 상태
즉, Context는 “공유 범위가 넓은 값”에는 강하지만 “변경 빈도가 높은 상태 저장소”로 쓰기에는 부담이 있습니다.
Provider는 작고 분명해야 한다
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(null);
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within ThemeProvider');
return context;
}
좋은 Context는 보통 역할이 하나입니다. AuthContext, ThemeContext, LocaleContext처럼 관심사를 나누면 변경 영향 범위를 줄이기 쉽습니다.
Context의 가장 큰 비용은 리렌더링이다
Provider의 value가 바뀌면 해당 Context를 구독하는 소비자들이 다시 렌더링됩니다. 이것이 Context를 무겁게 만드는 핵심 이유입니다.
실무에서는 아래 전략이 유용합니다.
- 하나의 거대한 Context 대신 여러 작은 Context로 분리
- 자주 바뀌는 값과 거의 고정된 값을 분리
- 객체를 매 렌더마다 새로 만들지 않도록 구조를 단순화
- 정말 자주 바뀌는 상태는 Zustand, Jotai 같은 도구 고려
인증 Context는 자주 쓰이지만 경계가 중요하다
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
async function login(email, password) {
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await res.json();
setUser(data.user);
}
function logout() {
setUser(null);
}
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
여기서 중요한 것은 인증 Context가 “로그인 API 호출기” 이상의 역할을 하려고 할 때입니다. 권한 체크, 토큰 갱신, 세션 복원, 라우팅 가드, 프로필 조회를 모두 한 Provider에 넣으면 빠르게 비대해집니다. 인증 관련 책임은 Context, API layer, router guard를 적절히 나누는 편이 좋습니다.
서버 상태는 Context보다 전용 도구가 더 낫다
자주 보는 실수는 API 응답 목록을 Context에 넣고 캐시처럼 사용하는 것입니다. 서버 상태는 만료, 재시도, 동기화, refetch 같은 문제가 함께 따라오므로 TanStack Query 같은 전용 도구가 더 적합합니다.
- Context: 앱 내부에서 공유하는 값
- Query 도구: 서버에서 가져와 동기화해야 하는 값
이 경계를 나누기 시작하면 프런트엔드 상태 설계가 훨씬 단순해집니다.
선택 기준
| 상황 | 더 적합한 선택 |
|---|---|
| 테마, 언어, 사용자 세션 | Context API |
| 비동기 서버 데이터 | TanStack Query |
| 자주 바뀌는 클라이언트 상태 | Zustand / Jotai |
| 강한 규칙과 큰 규모의 전역 상태 | Redux Toolkit |
자주 하는 실수
- 하나의 AppContext에 모든 상태를 집어넣는 방식
- 서버 데이터를 Context에 캐시처럼 쌓는 패턴
- Provider value에 매번 새 객체를 만들어 하위 리렌더링을 키우는 것
- 커스텀 훅 없이
useContext를 직접 남발하는 구조 - 상태와 액션, 권한 정책, 네트워크 호출을 한 Context에 섞는 방식
정리
React Context API는 작고 공통된 상태를 공유할 때 매우 강력합니다. 하지만 모든 전역 상태를 책임지는 범용 저장소로 쓰기 시작하면 설계와 성능 모두 빠르게 무거워집니다. Context는 “넓게 공유되지만 자주 바뀌지 않는 값”에 집중시키고, 서버 상태와 복잡한 상태 관리 문제는 다른 도구에 맡기는 것이 실무에서 훨씬 안정적입니다.
운영 환경에서 어려워지는 지점
- Context는 가벼워 보이지만 자주 바뀌는 상태를 담기 시작하면 넓은 provider가 불필요한 렌더링과 책임 은닉을 만든다.
- 편해서 쓴다는 이유로 context를 남용하면 어디서나 접근 가능하지만 어디서 책임지는지는 흐려진 상태가 된다.
- context끼리 암묵적으로 의존하면 provider 순서와 초기화 로직이 버그 원인이 된다.
중요한 아키텍처 결정
- context는 theme, auth 세션 형태, feature flag, dependency injection 같은 비교적 안정적인 횡단 관심사에 주로 쓴다.
- 자주 바뀌는 서버 유래 데이터는 context보다 전용 server-state 도구에 둔다.
- 거대한 app context 하나보다 목적이 분명한 여러 provider를 선호한다.
실무 예시
좋은 context는 원시 내부 상태보다 안정적인 인터페이스를 노출한다.
type AuthContextValue = {
userId: string | null
isAuthenticated: boolean
signOut: () => Promise<void>
}
const AuthContext = createContext<AuthContextValue | null>(null)
피해야 할 안티패턴
- 자주 업데이트되는 리스트나 대시보드 데이터를 전역 context에 넣는 것.
- context를 아키텍처 경계의 대체물로 쓰는 것.
- 안전한 접근 훅 없이 nullable context를 직접 export하는 것.
운영 체크리스트
- provider 주변의 불필요한 재렌더링 지점을 측정한다.
- provider 트리를 의도적으로 설계하고 문서화한다.
- 필요한 곳에서는 context 값과 callback 안정성을 유지한다.
- 해당 context가 वास्तव में server cache, route state, local state여야 하는지 다시 본다.
최종 판단
Context는 안정적인 공용 의존성과 설정 흐름에 매우 좋다. 빠르게 바뀌는 애플리케이션 상태의 기본 저장소로 쓰기에는 적합하지 않다.
Continue Reading
다음으로 읽기 좋은 글
스트리밍 UI의 실패 복구 패턴
부분 렌더링, 서버 스트리밍, AI 응답 UI에서 중간 실패를 사용자 경험으로 흡수하는 설계 방법을 정리합니다.
🖥️ FrontendOptimistic UI의 Reconciliation 경계
낙관적 UI는 빠르게 느껴지지만, 서버 진실과 충돌하는 순간 복잡성이 드러납니다. 어디까지 낙관적으로 처리할지 경계를 정리합니다.
📈 최신 동향React Foundation 이 엔지니어링 팀에 의미하는 것
React Foundation 소식이 단순 거버넌스 뉴스가 아니라 프레임워크 생태계의 장기 예측 가능성에 어떤 의미를 갖는지 정리한 글입니다.
🧪 TestReact Testing Library 실전 설계 가이드
React Testing Library를 사용자 중심 테스트 도구로 활용하는 방법을 정리합니다. query 우선순위, 상호작용 테스트, 비동기 UI, provider wrapper, 과도한 mocking 회피까지 실무 기준으로 다룹니다.
다음 탐색