[React] React.js 강좌 25. Context API로 전역 상태 관리하기
React를 쓰다 보면 언젠가 꼭 맞닥뜨리게 되는 고민이 있습니다.
“이 상태를 어디에 두는 게 맞을까?”
처음에는 각 컴포넌트 내부에서 useState로 관리하면 되지만,
점점 컴포넌트가 깊어지고, props로 데이터를 계속 내려주다 보면
코드가 복잡해지고 관리가 어려워집니다.
이때 등장하는 게 바로 Context API입니다.
상태를 전역으로 공유할 수 있게 도와주는,
React 기본 내장 기능이자 상태 관리의 시작점이라고 할 수 있습니다.
props drilling이 문제였던 시절
예전에 프로젝트를 할 때,
로그인한 사용자의 정보를 여러 페이지에서 보여줘야 했습니다.
헤더, 사이드바, 마이페이지 등 거의 모든 컴포넌트가 이 정보를 필요로 했죠.
처음엔 상위 컴포넌트에서 props로 내려주었는데,
중간에 연결만 담당하는 컴포넌트가 너무 많아졌습니다.
예를 들어 App → Layout → Header → UserMenu 식으로요.
이런 구조는 불필요한 리렌더링도 유발합니다.
사용자 정보가 바뀌면 전혀 관계없는 Layout까지 다시 렌더링되니까요.
이 문제를 해결한 게 바로 Context입니다.
한마디로, props 없이 전역적으로 데이터에 접근할 수 있는 통로를 만들어주는 개념입니다.
Context API의 기본 구조
Context를 사용하는 패턴은 아주 단순합니다.
createContext로 Context를 만들고,
Provider로 감싼 뒤,
useContext로 값을 가져옵니다.
아래 코드를 보면 바로 감이 잡힙니다.
import React, { createContext, useContext, useState } from 'react';
// 1. Context 생성
const UserContext = createContext();
// 2. Provider 컴포넌트 정의
export function UserProvider({ children }) {
const [user, setUser] = useState(null);
const login = (name) => setUser({ name });
const logout = () => setUser(null);
return (
<UserContext.Provider value={{ user, login, logout }}>
{children}
</UserContext.Provider>
);
}
// 3. Context를 사용하는 훅
export function useUser() {
return useContext(UserContext);
}
이제 어디서든 useUser()를 호출하면
user 상태와 login/logout 함수를 바로 쓸 수 있습니다.
실제 적용 예시 – 로그인 상태 전역 관리
위의 구조를 App 전체에 적용해보겠습니다.
import { UserProvider } from './UserContext';
import Header from './Header';
import Main from './Main';
function App() {
return (
<UserProvider>
<Header />
<Main />
</UserProvider>
);
}
이제 Header 컴포넌트에서 전역 상태를 바로 불러올 수 있습니다.
import { useUser } from './UserContext';
function Header() {
const { user, login, logout } = useUser();
return (
<header>
{user ? (
<>
<span>{user.name}님 반갑습니다!</span>
<button onClick={logout}>로그아웃</button>
</>
) : (
<button onClick={() => login('기범')}>로그인</button>
)}
</header>
);
}
이 구조의 가장 큰 장점은
어디서든 상태를 접근할 수 있고, props가 사라진다는 점입니다.
또한 불필요한 리렌더링도 줄어듭니다.
Context를 남용하면 생기는 문제
하지만 Context는 전역 상태라고 해서
모든 걸 여기에 넣는 건 좋지 않습니다.
예전에 제가 실수했던 게,
모든 UI 상태를 Context에 몰아넣은 적이 있었습니다.
모달 열림 여부, 입력 폼 값, 필터 조건 등…
결국 작은 변경에도 앱 전체가 리렌더링되면서 성능이 떨어졌습니다.
Context는 **“여러 곳에서 동시에 필요한 데이터”**에만 써야 합니다.
사용자 정보, 언어 설정, 테마(다크/라이트 모드) 같은 전역 상태가 그 예입니다.
일반적인 UI 상태는 여전히 로컬 useState로 관리하는 게 낫습니다.
Context와 Reducer를 함께 쓰면 더 깔끔해진다
상태가 조금 복잡해질 경우,
Context 안에서 useReducer를 쓰는 게 훨씬 구조적입니다.
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'TOGGLE':
return { ...state, dark: !state.dark };
default:
return state;
}
}, { dark: false });
return (
<ThemeContext.Provider value={{ state, dispatch }}>
{children}
</ThemeContext.Provider>
);
}
이렇게 하면 reducer를 따로 관리할 수 있어
상태 변경 로직이 명확해집니다.
Redux를 쓰지 않아도 충분히 유연한 구조가 됩니다.
프로젝트에서 적용해보며 느낀 점
Context를 본격적으로 사용하기 시작한 건
관리 도구를 만들던 시점이었습니다.
로그인한 사용자 정보, 권한(role), 접근 가능한 메뉴 목록을
여러 컴포넌트에서 공유해야 했기 때문이죠.
그때 Context를 도입하고 나서
props를 거의 쓰지 않아도 되는 구조가 만들어졌습니다.
게다가 전역 상태를 한 곳에서만 관리하니
협업할 때도 “이 상태 어디서 바꿔요?” 같은 질문이 사라졌습니다.
물론 나중에는 더 복잡한 상태를 위해
Zustand나 Redux Toolkit으로 넘어갔지만,
Context는 여전히 React의 기반이자 출발점이라고 생각합니다.
Context API는 “작은 프로젝트의 전역 상태 관리”에는 충분히 강력합니다.
불필요한 외부 라이브러리 없이도,
데이터 흐름을 한눈에 파악할 수 있는 장점이 있습니다.
다음 글에서는 이 전역 상태 개념을 확장해서,
Zustand로 더 구조적인 상태 관리를 하는 방법을 다뤄보겠습니다.
Context보다 조금 더 가볍고 직관적인 방식으로
프로젝트의 상태 흐름을 관리할 수 있는 방법을 함께 살펴보겠습니다.