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