Type something to search...

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까지 보내려면 모든 중간 컴포넌트를 거쳐야 함
2
function App() {
3
const [user, setUser] = useState('김철수');
4
return <Page user={user} />; // Page에 넘김
5
}
6
7
function Page({ user }) {
8
return <Header user={user} />; // Header에 넘김 (Page는 user를 쓰지 않음)
9
}
10
11
function Header({ user }) {
12
return <Profile user={user} />; // Profile에 넘김 (Header도 user를 쓰지 않음)
13
}
14
15
function Profile({ user }) {
16
return <div>{user}</div>; // 여기서만 실제로 사용
17
}

컴포넌트가 10단계라면 10번을 전달해야 한다. Context를 쓰면 이 문제가 해결된다.

1.2. Context 기본 사용법

Context는 3단계로 사용한다: ① 만들기 → ② 제공하기 → ③ 꺼내 쓰기

1.2.1. 1단계: Context 생성

createContext()로 데이터를 담을 그릇(Context 객체) 을 만든다. 이름은 자유롭게 붙여도 되지만 보통 무엇Context 형태로 짓는다.

1
import { createContext } from 'react';
2
3
const UserContext = createContext();
4
// UserContext라는 빈 그릇을 만들었다.
5
// 나중에 이 안에 데이터를 넣어서 아래 컴포넌트들에게 전달할 것이다.

1.2.2. 2단계: Provider로 값 제공

Context.Provider공유할 범위를 감싸고, value에 전달할 데이터를 넣는다. Provider 안에 있는 모든 컴포넌트는 이 값에 접근할 수 있다.

1
function 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가 제공한 값을 바로 꺼낼 수 있다. 몇 단계를 거치든 상관없이 직접 가져온다.

1
import { useContext } from 'react';
2
3
function Profile() {
4
// UserContext에서 값을 꺼낸다. Props 없이도 바로 사용 가능!
5
const user = useContext(UserContext);
6
7
return <div>사용자: {user}</div>;
8
}

1.3. 다크모드 구현 예제

Context를 실제로 활용하는 대표적인 사례다. 다크모드 상태를 Context에 넣어두면, 화면 어디서든 버튼 하나로 테마를 바꿀 수 있다.

전체 구조를 먼저 보면:

1
App
2
└── ThemeProvider ← isDark 상태와 toggleTheme 함수를 Context에 넣음
3
└── Layout ← isDark로 배경색 변경
4
├── ToggleButton ← toggleTheme으로 모드 전환
5
└── Content ← isDark로 텍스트 표시

1단계: Context 생성

1
import { createContext, useContext, useState } from 'react';
2
3
const ThemeContext = createContext();
4
// 테마 정보(isDark, toggleTheme)를 담을 그릇을 만든다

2단계: Provider 컴포넌트 만들기

상태 관리와 Provider 역할을 하나의 컴포넌트로 묶는다. children은 이 컴포넌트 안에 들어올 자식 컴포넌트들을 의미한다.

1
function 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 값에 따라 배경색과 글자색을 다르게 지정한다. 삼항연산자 조건 ? 참일때값 : 거짓일때값 을 사용한다.

1
function 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을 꺼내서 버튼 클릭 시 실행한다. 현재 모드에 따라 버튼 텍스트도 바꾼다.

1
function 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단계: 콘텐츠

1
function Content() {
2
const { isDark } = useContext(ThemeContext);
3
4
// 현재 어떤 모드인지 텍스트로 표시
5
return <p>현재 모드: {isDark ? '다크' : '라이트'}</p>;
6
}

6단계: 앱 루트에서 Provider로 감싸기

ThemeProvider로 감싸야 그 안의 모든 컴포넌트가 Context를 쓸 수 있다. 감싸지 않으면 useContext를 호출해도 값이 없어서 오류가 난다.

1
function App() {
2
return (
3
<ThemeProvider> {/* 이 안에 있는 컴포넌트는 모두 ThemeContext에 접근 가능 */}
4
<Layout />
5
</ThemeProvider>
6
);
7
}

핵심 포인트:

  • ThemeProviderisDarktoggleTheme을 Context에 넣어 전달
  • Layout, ToggleButton, Content 모두 Props 없이 바로 꺼내 사용
  • toggleThemeprev => !prev 패턴으로 이전 값을 기준으로 토글

1.4. 완료 (유저 예제)

Context로 유저 이름을 전역 관리하는 예제다. setUser도 함께 전달하면 하위 컴포넌트에서 값을 변경할 수도 있다.

1
import { createContext, useContext, useState } from 'react';
2
3
// Context 생성
4
const UserContext = createContext();
5
6
function 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을 렌더링할 뿐
18
function Page() {
19
return (
20
<div>
21
<h1>페이지</h1>
22
<Profile />
23
</div>
24
);
25
}
26
27
function 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
}