상태관리 비교 정리

Q1. Redux와 Context API의 차이는 무엇인가요?

답변

ReduxContext API는 모두 전역 상태 관리를 위한 도구이지만, 철학과 복잡도가 다릅니다.

Context API

Context API: React 내장 기능으로 간단한 전역 상태 공유

// ✅ Context API (간단)
import { createContext, useContext, useState } from 'react';

// 1. Context 생성
const ThemeContext = createContext();

// 2. Provider 컴포넌트
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

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

// 3. 사용
function Button() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Current theme: {theme}
    </button>
  );
}

// 4. App
function App() {
  return (
    <ThemeProvider>
      <Button />
    </ThemeProvider>
  );
}

장점:

  • 간단하고 직관적
  • 추가 라이브러리 불필요
  • 작은 앱에 적합

단점:

  • 성능 최적화 어려움
  • DevTools 없음
  • 미들웨어 없음

Redux

Redux: 예측 가능한 상태 관리를 위한 라이브러리

// Redux (복잡하지만 강력)
import { createStore } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux';

// 1. Action Types
const SET_THEME = 'SET_THEME';

// 2. Action Creators
const setTheme = (theme) => ({ type: SET_THEME, payload: theme });

// 3. Reducer
const initialState = { theme: 'light' };

function themeReducer(state = initialState, action) {
  switch (action.type) {
    case SET_THEME:
      return { ...state, theme: action.payload };
    default:
      return state;
  }
}

// 4. Store
const store = createStore(themeReducer);

// 5. 사용
function Button() {
  const theme = useSelector(state => state.theme);
  const dispatch = useDispatch();

  return (
    <button onClick={() => dispatch(setTheme(theme === 'light' ? 'dark' : 'light'))}>
      Current theme: {theme}
    </button>
  );
}

// 6. App
function App() {
  return (
    <Provider store={store}>
      <Button />
    </Provider>
  );
}

장점:

  • 예측 가능한 상태 변경
  • Redux DevTools (시간 여행 디버깅)
  • 미들웨어 (redux-thunk, redux-saga)
  • 대규모 앱에 적합

단점:

  • 보일러플레이트 코드 많음
  • 학습 곡선 높음
  • 단순한 기능에는 과함

비교표

특징Context APIRedux
설치내장외부 라이브러리
보일러플레이트적음많음
DevTools없음Redux DevTools
미들웨어없음redux-thunk, saga
성능 최적화어려움Selector 최적화
학습 곡선낮음높음
적합한 규모소규모대규모

꼬리 질문 1: Context API의 성능 문제는?

문제: Context 값이 변경되면 모든 Consumer가 리렌더링됩니다.

// ❌ 성능 문제
const AppContext = createContext();

function AppProvider({ children }) {
  const [user, setUser] = useState({ name: 'John' });
  const [theme, setTheme] = useState('light');

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

// Theme만 사용하는 컴포넌트
function ThemeButton() {
  const { theme, setTheme } = useContext(AppContext);

  console.log('ThemeButton 렌더링');
  return <button onClick={() => setTheme('dark')}>{theme}</button>;
}

// User만 사용하는 컴포넌트
function UserProfile() {
  const { user } = useContext(AppContext);

  console.log('UserProfile 렌더링');
  return <div>{user.name}</div>;
}

// 문제:
// user 변경 → ThemeButton도 리렌더링! ⚠️
// theme 변경 → UserProfile도 리렌더링! ⚠️

해결 1: Context 분리:

// ✅ Context 분리
const UserContext = createContext();
const ThemeContext = createContext();

function AppProvider({ children }) {
  const [user, setUser] = useState({ name: 'John' });
  const [theme, setTheme] = useState('light');

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

// Theme만 사용
function ThemeButton() {
  const { theme, setTheme } = useContext(ThemeContext);
  // user 변경 시 리렌더링 안 됨! ✅
}

// User만 사용
function UserProfile() {
  const { user } = useContext(UserContext);
  // theme 변경 시 리렌더링 안 됨! ✅
}

해결 2: useMemo로 value 최적화:

// ✅ useMemo로 최적화
function AppProvider({ children }) {
  const [user, setUser] = useState({ name: 'John' });
  const [theme, setTheme] = useState('light');

  const userValue = useMemo(() => ({ user, setUser }), [user]);
  const themeValue = useMemo(() => ({ theme, setTheme }), [theme]);

  return (
    <UserContext.Provider value={userValue}>
      <ThemeContext.Provider value={themeValue}>
        {children}
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

꼬리 질문 2: Redux의 단방향 데이터 플로우란?

Redux의 데이터 플로우:

┌─────────────────────────────────────────┐
│                                         │
│  Action                                 │
│  { type: 'INCREMENT', payload: 1 }     │
│         ↓                               │
│  Dispatch                               │
│  dispatch(action)                       │
│         ↓                               │
│  Middleware (optional)                  │
│  redux-thunk, redux-saga                │
│         ↓                               │
│  Reducer                                │
│  (state, action) => newState            │
│         ↓                               │
│  Store                                  │
│  { count: 1 }                           │
│         ↓                               │
│  Component (re-render)                  │
│  useSelector(state => state.count)      │
│                                         │
└─────────────────────────────────────────┘

예시:

// 1. Action 발생
dispatch({ type: 'INCREMENT', payload: 1 });

// 2. Reducer 실행
function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + action.payload };
    default:
      return state;
  }
}

// 3. Store 업데이트
// Before: { count: 0 }
// After:  { count: 1 }

// 4. Component 리렌더링
function Counter() {
  const count = useSelector(state => state.count);
  return <div>{count}</div>;  // 1로 업데이트됨
}


👉 다음 편: 상태 관리 (Part 2: Redux Toolkit, 실무 패턴)