7_useContext로 전역 상태 관리
코드 블록의 Try it Yourself 버튼으로 직접 실행할 수 있다.
구문
1. useContext로 전역 상태 관리
1.1. Context가 필요한 이유
리액트에서 데이터는 보통 부모 → 자식 방향으로 Props를 통해 전달한다. 그런데 컴포넌트가 깊게 중첩되어 있으면, 중간에 있는 컴포넌트들이 직접 쓰지도 않는 데이터를 계속 받아서 아래로 넘겨줘야 한다. 이 문제를 Prop Drilling(프롭 드릴링) 이라고 한다.
Context를 사용하면 중간 컴포넌트를 거치지 않고, 필요한 컴포넌트가 직접 데이터를 꺼내 쓸 수 있다.
1.1.1. Props 전달의 문제점 (Prop Drilling)
user 데이터가 App에 있는데, 실제로 사용하는 곳은 맨 아래의 Profile이다.
중간의 Page, Header는 데이터를 쓰지 않지만 그냥 아래로 전달하는 역할만 한다.
// user를 Profile까지 보내려면 모든 중간 컴포넌트를 거쳐야 함
function App() {
const [user, setUser] = useState('김철수');
return <Page user={user} />; // Page에 넘김
}
function Page({ user }) {
return <Header user={user} />; // Header에 넘김 (Page는 user를 쓰지 않음)
}
function Header({ user }) {
return <Profile user={user} />; // Profile에 넘김 (Header도 user를 쓰지 않음)
}
function Profile({ user }) {
return <div>{user}</div>; // 여기서만 실제로 사용
}
컴포넌트가 10단계라면 10번을 전달해야 한다. Context를 쓰면 이 문제가 해결된다.
1.2. Context 기본 사용법
Context는 3단계로 사용한다: ① 만들기 → ② 제공하기 → ③ 꺼내 쓰기
1.2.1. 1단계: Context 생성
createContext()로 데이터를 담을 그릇(Context 객체) 을 만든다.
이름은 자유롭게 붙여도 되지만 보통 무엇Context 형태로 짓는다.
import { createContext } from 'react';
const UserContext = createContext();
// UserContext라는 빈 그릇을 만들었다.
// 나중에 이 안에 데이터를 넣어서 아래 컴포넌트들에게 전달할 것이다.
1.2.2. 2단계: Provider로 값 제공
Context.Provider로 공유할 범위를 감싸고, value에 전달할 데이터를 넣는다.
Provider 안에 있는 모든 컴포넌트는 이 값에 접근할 수 있다.
function App() {
const [user, setUser] = useState('김철수');
return (
// UserContext.Provider로 감싸면 안에 있는 모든 컴포넌트가 user를 쓸 수 있다
<UserContext.Provider value={user}>
<Page />
{/* Page, Page 안의 Header, Header 안의 Profile 모두 접근 가능 */}
</UserContext.Provider>
);
}
1.2.3. 3단계: useContext로 값 사용
useContext(Context이름)을 호출하면 Provider가 제공한 값을 바로 꺼낼 수 있다.
몇 단계를 거치든 상관없이 직접 가져온다.
import { useContext } from 'react';
function Profile() {
// UserContext에서 값을 꺼낸다. Props 없이도 바로 사용 가능!
const user = useContext(UserContext);
return <div>사용자: {user}</div>;
}
1.3. 다크모드 구현 예제
Context를 실제로 활용하는 대표적인 사례다. 다크모드 상태를 Context에 넣어두면, 화면 어디서든 버튼 하나로 테마를 바꿀 수 있다.
전체 구조를 먼저 보면:
App
└── ThemeProvider ← isDark 상태와 toggleTheme 함수를 Context에 넣음
└── Layout ← isDark로 배경색 변경
├── ToggleButton ← toggleTheme으로 모드 전환
└── Content ← isDark로 텍스트 표시
1단계: Context 생성
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
// 테마 정보(isDark, toggleTheme)를 담을 그릇을 만든다
2단계: Provider 컴포넌트 만들기
상태 관리와 Provider 역할을 하나의 컴포넌트로 묶는다.
children은 이 컴포넌트 안에 들어올 자식 컴포넌트들을 의미한다.
function ThemeProvider({ children }) {
// isDark: 현재 다크모드인지 여부 (기본값: false = 라이트모드)
const [isDark, setIsDark] = useState(false);
// 버튼을 누를 때마다 true ↔ false 를 번갈아 바꾸는 함수
// prev는 현재 값이다. !prev 는 반대값을 의미한다
const toggleTheme = () => setIsDark(prev => !prev);
return (
// isDark(현재 모드)와 toggleTheme(변경 함수) 두 가지를 함께 전달
<ThemeContext.Provider value={{ isDark, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
3단계: isDark로 스타일 변경하기
isDark 값에 따라 배경색과 글자색을 다르게 지정한다.
삼항연산자 조건 ? 참일때값 : 거짓일때값 을 사용한다.
function Layout() {
// ThemeContext에서 isDark만 꺼낸다
const { isDark } = useContext(ThemeContext);
const style = {
background: isDark ? '#1a1a1a' : '#ffffff', // 다크: 어두운 회색 / 라이트: 흰색
color: isDark ? '#ffffff' : '#000000', // 다크: 흰 글자 / 라이트: 검정 글자
minHeight: '100vh',
padding: '20px',
};
return (
<div style={style}>
<ToggleButton />
<Content />
</div>
);
}
4단계: 토글 버튼
toggleTheme을 꺼내서 버튼 클릭 시 실행한다.
현재 모드에 따라 버튼 텍스트도 바꾼다.
function ToggleButton() {
// isDark와 toggleTheme 둘 다 꺼낸다
const { isDark, toggleTheme } = useContext(ThemeContext);
return (
// 버튼을 누르면 toggleTheme 실행 → isDark가 반전됨
<button onClick={toggleTheme}>
{isDark ? '☀️ 라이트 모드' : '🌙 다크 모드'}
{/* 다크모드일 때: 라이트 모드로 바꾸는 버튼 표시 */}
{/* 라이트모드일 때: 다크 모드로 바꾸는 버튼 표시 */}
</button>
);
}
5단계: 콘텐츠
function Content() {
const { isDark } = useContext(ThemeContext);
// 현재 어떤 모드인지 텍스트로 표시
return <p>현재 모드: {isDark ? '다크' : '라이트'}</p>;
}
6단계: 앱 루트에서 Provider로 감싸기
ThemeProvider로 감싸야 그 안의 모든 컴포넌트가 Context를 쓸 수 있다.
감싸지 않으면 useContext를 호출해도 값이 없어서 오류가 난다.
function App() {
return (
<ThemeProvider> {/* 이 안에 있는 컴포넌트는 모두 ThemeContext에 접근 가능 */}
<Layout />
</ThemeProvider>
);
}
핵심 포인트:
ThemeProvider가isDark와toggleTheme을 Context에 넣어 전달Layout,ToggleButton,Content모두 Props 없이 바로 꺼내 사용toggleTheme은prev => !prev패턴으로 이전 값을 기준으로 토글
1.4. 완료 (유저 예제)
Context로 유저 이름을 전역 관리하는 예제다.
setUser도 함께 전달하면 하위 컴포넌트에서 값을 변경할 수도 있다.
import { createContext, useContext, useState } from 'react';
// Context 생성
const UserContext = createContext();
function App() {
const [user, setUser] = useState('김철수');
return (
// user(현재 이름)와 setUser(변경 함수)를 함께 전달
<UserContext.Provider value={{ user, setUser }}>
<Page />
</UserContext.Provider>
);
}
// Page는 user를 직접 쓰지 않는다. 그냥 Profile을 렌더링할 뿐
function Page() {
return (
<div>
<h1>페이지</h1>
<Profile />
</div>
);
}
function Profile() {
// Props를 받지 않았는데도 user와 setUser를 바로 사용할 수 있다
const { user, setUser } = useContext(UserContext);
return (
<div>
<p>사용자: {user}</p>
{/* 버튼 클릭 시 setUser로 이름을 변경 → 화면이 자동으로 다시 그려진다 */}
<button onClick={() => setUser('이영희')}>이름 변경</button>
</div>
);
}