7회차 - useContext로 전역 상태 관리

1. Context가 필요한 이유

Props를 여러 단계로 전달하는 것은 번거롭다. Context를 사용하면 전역적으로 데이터를 공유할 수 있다.

Props 전달의 문제점

function App() {
  const [user, setUser] = useState('김철수');
  return <Page user={user} />;
}

function Page({ user }) {
  return <Header user={user} />;
}

function Header({ user }) {
  return <Profile user={user} />;
}

function Profile({ user }) {
  return <div>{user}</div>;
}

// user를 3단계나 전달해야 한다!

2. Context 기본 사용법

1단계: Context 생성

import { createContext } from 'react';

const UserContext = createContext();

2단계: Provider로 값 제공

function App() {
  const [user, setUser] = useState('김철수');

  return (
    <UserContext.Provider value={user}>
      <Page />
    </UserContext.Provider>
  );
}

3단계: useContext로 값 사용

import { useContext } from 'react';

function Profile() {
  const user = useContext(UserContext);
  return <div>{user}</div>;
}

3. 완전한 예제

import { createContext, useContext, useState } from 'react';

// Context 생성
const UserContext = createContext();

function App() {
  const [user, setUser] = useState('김철수');

  return (
    <UserContext.Provider value={user}>
      <Page />
    </UserContext.Provider>
  );
}

function Page() {
  return (
    <div>
      <h1>페이지</h1>
      <Profile />
    </div>
  );
}

function Profile() {
  const user = useContext(UserContext);
  return <div>사용자: {user}</div>;
}

4. 여러 값 전달하기

객체를 사용하면 여러 값을 한 번에 전달할 수 있다.

const UserContext = createContext();

function App() {
  const [user, setUser] = useState('김철수');
  const [age, setAge] = useState(25);

  return (
    <UserContext.Provider value={{ user, age, setUser, setAge }}>
      <Page />
    </UserContext.Provider>
  );
}

function Profile() {
  const { user, age, setUser } = useContext(UserContext);

  return (
    <div>
      <p>이름: {user}, 나이: {age}</p>
      <button onClick={() => setUser('이영희')}>이름 변경</button>
    </div>
  );
}

5. Context 파일 분리

Context를 별도 파일로 만들면 재사용하기 쉽다.

UserContext.jsx

import { createContext, useState } from 'react';

export const UserContext = createContext();

export function UserProvider({ children }) {
  const [user, setUser] = useState('김철수');
  const [age, setAge] = useState(25);

  return (
    <UserContext.Provider value={{ user, age, setUser, setAge }}>
      {children}
    </UserContext.Provider>
  );
}

App.jsx

import { UserProvider } from './UserContext';

function App() {
  return (
    <UserProvider>
      <Page />
    </UserProvider>
  );
}

Profile.jsx

import { useContext } from 'react';
import { UserContext } from './UserContext';

function Profile() {
  const { user, age } = useContext(UserContext);
  return <div>{user}, {age}살</div>;
}

6. Custom Hook 만들기

useContext를 감싸는 커스텀 훅을 만들면 더 편리하다.

// UserContext.jsx
import { createContext, useContext, useState } from 'react';

const UserContext = createContext();

export function UserProvider({ children }) {
  const [user, setUser] = useState('김철수');

  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
}

// Custom Hook
export function useUser() {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('UserProvider 안에서 사용해야 합니다');
  }
  return context;
}

// 사용
function Profile() {
  const { user, setUser } = useUser();
  return <div>{user}</div>;
}

7. 실습: 테마 전환 기능

import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggle = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggle }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  return useContext(ThemeContext);
}

// App.jsx
function App() {
  return (
    <ThemeProvider>
      <Page />
    </ThemeProvider>
  );
}

function Page() {
  const { theme, toggle } = useTheme();

  const style = {
    background: theme === 'light' ? '#fff' : '#333',
    color: theme === 'light' ? '#000' : '#fff',
    padding: '20px'
  };

  return (
    <div style={style}>
      <h1>현재 테마: {theme}</h1>
      <button onClick={toggle}>테마 변경</button>
      <Content />
    </div>
  );
}

function Content() {
  const { theme } = useTheme();
  return <p>{theme} 모드입니다.</p>;
}

8. 실습: 장바구니 앱

import { createContext, useContext, useState } from 'react';

const CartContext = createContext();

export function CartProvider({ children }) {
  const [items, setItems] = useState([]);

  const add = (item) => {
    setItems([...items, item]);
  };

  const remove = (id) => {
    setItems(items.filter(item => item.id !== id));
  };

  return (
    <CartContext.Provider value={{ items, add, remove }}>
      {children}
    </CartContext.Provider>
  );
}

export function useCart() {
  return useContext(CartContext);
}

// 사용
function ProductList() {
  const { add } = useCart();

  const products = [
    { id: 1, name: '사과', price: 1000 },
    { id: 2, name: '바나나', price: 1500 }
  ];

  return (
    <div>
      {products.map(p => (
        <div key={p.id}>
          {p.name} - {p.price}원
          <button onClick={() => add(p)}>담기</button>
        </div>
      ))}
    </div>
  );
}

function Cart() {
  const { items, remove } = useCart();
  const total = items.reduce((sum, item) => sum + item.price, 0);

  return (
    <div>
      <h2>장바구니</h2>
      {items.map((item, i) => (
        <div key={i}>
          {item.name} - {item.price}원
          <button onClick={() => remove(item.id)}>삭제</button>
        </div>
      ))}
      <p>총액: {total}원</p>
    </div>
  );
}

function App() {
  return (
    <CartProvider>
      <ProductList />
      <Cart />
    </CartProvider>
  );
}

9. 여러 Context 사용하기

function App() {
  return (
    <UserProvider>
      <ThemeProvider>
        <CartProvider>
          <Page />
        </CartProvider>
      </ThemeProvider>
    </UserProvider>
  );
}

function Page() {
  const { user } = useUser();
  const { theme } = useTheme();
  const { items } = useCart();

  return (
    <div>
      <p>사용자: {user}</p>
      <p>테마: {theme}</p>
      <p>장바구니: {items.length}개</p>
    </div>
  );
}

10. 주의사항

11. 연습 과제

  1. 언어 전환 기능 만들기 (한국어/영어)
  2. 로그인 상태 관리 Context 만들기
  3. 알림 메시지 시스템 만들기
  4. 여러 페이지에서 사용하는 설정 Context 만들기

정리