Context API로 Props drilling 없이 전역 상태를 공유하는 방법, Provider 패턴 설계, 그리고 Context 남용을 피하는 기준을 배웁니다.
🌐 Props Drilling — 전달 피로
컴포넌트 트리가 깊어지면 Props Drilling이 발생합니다. 최상위 컴포넌트의 데이터가 최하위까지 닿으려면 중간에 있는 모든 컴포넌트가 Props를 전달해야 합니다. 중간 컴포넌트들은 그 데이터를 실제로 쓰지도 않으면서 전달만 하게 됩니다.
// ❌ Props Drilling
function App() {
const [user, setUser] = useState<User | null>(null)
return <Layout user={user} /> // Layout은 user를 쓰지 않지만 전달해야 함
}
function Layout({ user }: { user: User | null }) {
return <Header user={user} /> // Header도 마찬가지
}
function Header({ user }: { user: User | null }) {
return <UserMenu user={user} /> // 실제로 쓰는 곳은 여기뿐
}
function UserMenu({ user }: { user: User | null }) {
return <span>{user?.name}</span> // 여기서 비로소 사용
}
이것이 Context API가 해결하려는 문제입니다.
⚙️ Context 만들기 — 3단계
Context 설정은 세 단계입니다: ① Context 생성 → ② Provider로 공급 → ③ useContext로 소비.
// contexts/AuthContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react'
interface User {
id: string
name: string
email: string
}
interface AuthContextType {
user: User | null
login: (user: User) => void
logout: () => void
}
// ① Context 생성
const AuthContext = createContext<AuthContextType | null>(null)
// ② Provider 컴포넌트
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const login = (user: User) => setUser(user)
const logout = () => setUser(null)
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
)
}
// ③ 소비 훅 (선택이지만 권장)
export function useAuth(): AuthContextType {
const context = useContext(AuthContext)
if (!context) throw new Error('useAuth는 AuthProvider 안에서 사용해야 합니다')
return context
}
🔌 Provider 등록과 사용
// main.tsx 또는 App.tsx에서 Provider로 감싸기
import { AuthProvider } from './contexts/AuthContext'
function App() {
return (
<AuthProvider>
<Layout /> {/* 아래 모든 컴포넌트에서 useAuth() 사용 가능 */}
</AuthProvider>
)
}
// 어디서든 바로 사용 — Props 없이!
function UserMenu() {
const { user, logout } = useAuth()
if (!user) return null
return (
<div className="flex items-center gap-3">
<span className="text-sm font-medium">{user.name}</span>
<button onClick={logout} className="text-sm text-gray-500 hover:text-red-500">
로그아웃
</button>
</div>
)
}
🎨 여러 Context 조합하기
실제 앱에서는 여러 Context를 함께 씁니다. 각 Context는 하나의 관심사에 집중합니다:
// ContextProviders.tsx — 모든 Provider를 한 곳에서 합성
function ContextProviders({ children }: { children: ReactNode }) {
return (
<AuthProvider>
<ThemeProvider>
<LanguageProvider>
{children}
</LanguageProvider>
</ThemeProvider>
</AuthProvider>
)
}
// App.tsx에서 단 하나만 등록
function App() {
return (
<ContextProviders>
<Layout />
</ContextProviders>
)
}
⚠️ Context 남용 금지 — 언제 쓰고 언제 피할까
| Context가 적합한 경우 | Context가 과한 경우 |
|---|---|
| 인증 정보 (user, token) | 컴포넌트 1-2개만 사용하는 상태 |
| 테마 (dark/light) | 자주 바뀌는 상태 (성능 문제) |
| 언어/지역 설정 | form 상태 (로컬로 충분) |
| 전역 알림/토스트 | 부모-자식 1단계 차이 |
Context value가 바뀌면 해당 Context를 소비하는 모든 컴포넌트가 리렌더링됩니다. 자주 변하는 상태(마우스 위치, 스크롤 등)를 Context에 넣으면 성능 문제가 생깁니다. 그런 경우에는 Zustand 같은 외부 상태 관리 라이브러리를 고려하세요.
✅ 정리
- Context API: Props Drilling 없이 컴포넌트 트리 어디서든 데이터를 공유합니다.
- 단계:
createContext→Provider로 감싸기 →useContext로 소비. - 커스텀 훅(
useAuth)으로 Context 접근을 캡슐화하면 가독성이 좋아집니다. - 인증·테마·언어처럼 넓은 범위에서 쓰이는 데이터에만 사용하세요.
관련 주제
- use 접두사 규칙
- 커스텀 훅 설계
- 반복 로직 추출
- useToggle 예시
- 개발·프로그래밍
- 개발·프로그래밍 강의
- 리액트 입문 — 컴포넌트로 만드는 웹
- 무료강의
- 무료 온라인 강의
- NUGUNA
- 누구나
댓글
0/1000
불러오는 중...
