useContext는 createContext를 통해 생성된 컨텍스트의 데이터에 함수형 컴포넌트가 직접 액세스할 수 있도록 하는 React 훅입니다. React의 컨텍스트는 props drilling 문제 — 이 데이터를 자체적으로 사용하지 않는 많은 중간 컴포넌트를 통해 props를 전달하는 것 — 를 해결합니다. React Documentation (2025)에 따르면, useContext는 컨텍스트 객체를 받아 컴포넌트 트리 상단에서 가장 가까운 Provider가 설정한 현재 값을 반환합니다. Provider의 값이 변경되면 useContext를 사용하는 모든 컴포넌트가 자동으로 다시 렌더링됩니다.
핵심 사항
useContext는 React 16.8에서 다른 훅들과 함께 추가된, React 컨텍스트에서 값을 읽을 수 있게 해주는 훅입니다. 컨텍스트는 각 수준에서 수동으로 props를 전달할 필요 없이 컴포넌트 트리를 통해 데이터를 전달하도록 설계된 React 내장 메커니즘입니다. useContext는 이전 Context API의 Consumer 컴포넌트를 대체하며 코드를 더 간결하고 읽기 쉽게 만듭니다.
컨텍스트의 일반적인 사용 사례에는 테마(라이트/다크), 로케일 및 번역(i18n), 사용자 인증, 애플리케이션 설정 및 다양한 중첩 수준의 많은 컴포넌트가 필요로 하는 기타 글로벌 데이터가 포함됩니다. React 팀은 컴포넌트 서브트리에 대해 전역적인 데이터에는 컨텍스트를 사용하지만, 전체 애플리케이션에 대해서는 사용하지 않도록 권장합니다.
React Team — 컨텍스트 문서 (2025)에 따르면, 컨텍스트의 잘못된 사용은 React 애플리케이션에서 성능 문제의 주요 원인 중 하나입니다. Provider의 값이 변경될 때마다 데이터의 어떤 부분이 변경되었는지와 관계없이 모든 소비자가 재렌더링됩니다. useMemo를 통한 최적화와 컨텍스트 분할이 이 문제를 해결합니다.
import { createContext, useContext } from 'react';
// 기본값으로 컨텍스트 생성
const ThemeContext = createContext('라이트');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={`btn-${theme}`}>Click</button>;
}
React의 컨텍스트 메커니즘은 Provider-Consumer 패턴을 통해 구현됩니다. createContext는 두 엔티티를 가진 객체를 반환합니다: Provider — 값을 전달하는 컴포넌트, 그리고 useContext에서 사용되는 컨텍스트 객체 자체입니다. Provider는 컴포넌트 트리에 마운트되며 중첩 깊이에 관계없이 모든 자식 요소에 값을 전달합니다.
React가 useContext 호출을 만나면 파이버 트리를 탐색하여 해당 컨텍스트에 가장 가까운 Provider를 찾습니다. Provider가 발견되면 그 값이 반환됩니다. Provider가 발견되지 않으면 createContext에 전달된 기본값이 반환됩니다. 이 검색은 모든 렌더링에서 발생하지만 파이버 노드 메모이제이션 덕분에 매우 빠르며 성능에 영향을 미치지 않습니다.
React — 컨텍스트 내부 구조 (2024)에 따르면, useContext의 내부 구현은 useState와 유사하게 훅의 연결 리스트를 사용합니다. 각 훅은 파이버 노드에 대한 참조를 저장하여 React가 어떤 Provider가 해당 컨텍스트에 해당하는지 빠르게 결정할 수 있도록 합니다. Provider가 값을 업데이트하면 React는 해당 컨텍스트를 사용하는 모든 파이버 노드를 재렌더링용으로 표시합니다.
Provider 컴포넌트는 서로 중첩되어 컨텍스트의 계층 구조를 만들 수 있습니다. 각 자식 Provider는 자체 서브트리에 대해 부모의 값을 재정의합니다. 이는 한 화면에서 라이트 테마가 필요하고 중첩된 모달 창에서 다크 테마가 필요할 때 유용합니다. useContext는 항상 트리 상단에서 가장 가까운 Provider의 값을 반환합니다.
const UserContext = createContext(null);
const ThemeContext = createContext('라이트');
function App() {
return (
<UserContext.Provider value={{ name: 'Alice' }}>
<ThemeContext.Provider value='dark'>
<Profile />
</ThemeContext.Provider>
</UserContext.Provider>
);
}
createContext(defaultValue) 함수는 컨텍스트 객체를 생성합니다. defaultValue 매개변수는 컴포넌트가 useContext를 호출했지만 트리 상단에 해당 Provider가 없을 때 사용됩니다. defaultValue가 없으면 useContext는 undefined를 반환하며, 이는 예기치 않은 오류로 이어질 수 있습니다. 항상 의미 있는 기본값이나 null을 전달하는 것이 좋습니다.
사용자 정의 Provider를 만드는 것은 컨텍스트 로직을 캡슐화하는 일반적인 패턴입니다. 이러한 Provider 내부에서는 상태가(useState 또는 useReducer를 통해) 저장되고 Provider의 value prop을 통해 제공됩니다. 이는 소비자 컴포넌트로부터 구현 세부 사항을 숨기고 컨텍스트 관리 로직을 한 곳에 집중시킵니다.
// 상태 관리가 있는 사용자 정의 Provider
const AuthContext = createContext(null);
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = useCallback(async (email, pass) => {
const u = await loginApi(email, pass);
setUser(u);
}, []);
return (
<AuthContext.Provider value={{ user, login }}>
{children}
</AuthContext.Provider>
);
}
함수형 컴포넌트에서 useContext는 컨텍스트에 액세스하는 유일한 방법입니다. 이는 render-prop 패턴이 필요했던 이전 Context API의 Consumer 컴포넌트를 대체합니다: <ThemeContext.Consumer>{value => ...}</ThemeContext.Consumer>. useContext는 코드를 더 선형적이고 읽기 쉽게 만들며, 특히 하나의 컴포넌트에서 여러 컨텍스트를 사용할 때 유용합니다.
하나의 컴포넌트에서 여러 컨텍스트를 사용할 때는 각 컨텍스트에 대해 useContext를 여러 번 호출하면 됩니다. 각 호출은 해당 Provider의 값을 반환합니다. 각 컨텍스트는 독립적인 엔티티이므로 호출 순서는 중요하지 않습니다. React는 동일한 파이버 참조 시스템을 통해 여러 호출을 최적화합니다.
function Dashboard() {
const { user } = useContext(AuthContext);
const theme = useContext(ThemeContext);
const { locale } = useContext(I18nContext);
return (
<div className={`dashboard-${theme}`}>
<h1>{locale.greeting}, {user.name}</h1>
</div>
);
}
useContext와 Redux 사이의 선택은 상태 관리의 규모와 복잡성에 따라 달라집니다. useContext + useReducer는 소규모 및 중간 규모 애플리케이션을 위한 Redux의 경량 대체재입니다. 외부 라이브러리 설치가 필요 없고, 배우기 쉬우며, 대부분의 작업에 충분합니다. Redux는 미들웨어, 개발자 도구 및 불변 업데이트를 포함한 엄격한 아키텍처가 필요할 때 정당화됩니다.
useContext에 비한 Redux의 주요 장점은 재렌더링 최적화입니다. 기본적으로 Provider의 값이 변경되면 모든 컨텍스트 소비자가 재렌더링됩니다. Redux는 useSelector와 shallowEqual을 사용하여 컴포넌트가 상태의 특정 부분만 구독할 수 있도록 하여 대규모 애플리케이션에서 재렌더링 횟수를 크게 줄입니다. 컨텍스트도 여러 개의 작은 컨텍스트로 분할하여 최적화할 수 있습니다.
| 기준 | useContext | Redux |
|---|---|---|
| 복잡성 | 외부 종속성 없음 | 스토어 및 미들웨어 설정 필요 |
| 재렌더링 | 변경 시 모든 소비자 | 특정 slice를 구독한 경우만 |
| DevTools | React DevTools | 타임트래블 지원 Redux DevTools |
| 미들웨어 | 지원 안 함 | Redux Thunk, Saga, Observable |
| 선택 시기 | 중간 규모 앱, 3–5 컨텍스트 | 복잡한 비즈니스 로직의 대규모 앱 |
Redux 관리자 — Redux 사용 시기 (2024)에 따르면, React 애플리케이션의 70%는 Redux가 필요하지 않습니다. 컴포넌트가 50개 미만이고 상태에 캐싱, 디바운스 및 사이드 이펙트를 포함한 복잡한 로직이 없는 경우 — useContext + useReducer로 충분합니다. Redux는 보일러플레이트를 추가하므로 신중하게 사용해야 합니다.
가장 흔한 실수는 모든 Provider 렌더링에서 value 객체를 다시 생성하는 것입니다. Provider에 value={{ user, login }}을 전달하면 Provider가 렌더링될 때마다 새 객체가 생성되어 데이터가 변경되지 않았어도 모든 소비자가 재렌더링됩니다. 해결책은 useMemo로 값을 메모이제이션하거나 자주 변경되는 데이터와 드물게 변경되는 데이터에 별도의 컨텍스트를 사용하는 것입니다.
// ❌ 모든 렌더링에서 새 객체 — 모든 소비자 재렌더링
<AuthContext.Provider value={{ user, login }}>{children}</AuthContext.Provider>
// ✅ 메모이제이션된 값 — user 또는 login이 변경될 때만 재렌더링
const authValue = useMemo(() => ({ user, login }), [user, login]);
<AuthContext.Provider value={authValue}>{children}</AuthContext.Provider>
“큰 컨텍스트” 문제를 해결하려면 전역 상태를 논리적 그룹으로 분할하세요: AuthContext, ThemeContext, I18nContext. 각 컨텍스트는 자체 영역을 담당하며 독립적으로 업데이트됩니다. 이는 useMemo를 통해 하나의 거대한 컨텍스트를 최적화하려는 것보다 간단하며 더 예측 가능한 재렌더링 동작을 제공합니다.
자주 묻는 질문
네, Provider의 value에 변경 함수를 전달하면 가능합니다. 일반적인 패턴은 Provider에 상태를 저장하고 useContext를 통해 데이터와 업데이트 함수를 모두 전달하는 것입니다. 자식 컴포넌트가 이 함수들을 호출하면 Provider의 상태 변경이 자동으로 모든 소비자를 업데이트합니다. 이는 소규모 애플리케이션을 위한 기본적인 Redux 대체재입니다.
간단한 시나리오에서는 useContext가 스토어와 미들웨어의 오버헤드가 없어 더 빠릅니다. 그러나 빈번한 업데이트와 많은 소비자가 있는 경우 Redux의 선택자(useSelector)가 상태의 특정 부분만 구독하는 반면, useContext는 모든 변경에서 모든 소비자를 재렌더링하므로 Redux가 유리합니다. 업데이트 빈도가 높은 애플리케이션(애니메이션, 실시간)의 경우 Redux 또는 전문 라이브러리를 선택하세요.
아니요. useContext는 모든 훅과 마찬가지로 React 함수형 컴포넌트 또는 사용자 정의 훅 내에서만 호출할 수 있습니다. 일반 함수(예: 유틸리티 또는 서비스)에서 컨텍스트 값을 가져와야 하는 경우 컴포넌트에서 매개변수로 전달하거나 React 외부에서 전역 상태를 가진 별도의 모듈을 사용하세요.
TypeScript에서 컨텍스트 타이핑은 createContext에 타입을 지정하여 수행합니다: createContext<AuthContextType | null>(null). 이렇게 하면 useContext(AuthContext)가 올바른 타입의 값을 반환합니다. 편리한 패턴은 useContext를 호출하고 null을 확인한 후 “useAuth must be used within AuthProvider”라는 명확한 오류를 발생시키는 사용자 정의 useAuth 훅을 만드는 것입니다.
가장 흔한 이유는 소비자 컴포넌트가 해당 Provider 내부에 없기 때문입니다. Provider가 useContext가 사용되는 전체 서브트리를 감싸고 있는지 확인하세요. 두 번째 이유 — Provider에 다른 컨텍스트 객체가 전달된 경우입니다: 개발자가 createContext를 호출하여 컨텍스트를 만들었지만 다른 createContext 인스턴스와 함께 useContext를 사용하고 있습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.