본문 바로가기
  • 코딩, 허쌤이 떠먹여 줄게
FrontEnd/React

useContext 테마바꾸기

by 허쌤 2026. 3. 28.

app.jsx

import { AppProvider, useApp } from './context/AppContext';
import Toolbar from './components/Toolbar';
import ContentPanel from './components/ContentPanel';

/**
 * Context 방식: 상태는 Provider 안에 있고,
 * 자식은 useApp()으로 접근합니다 (props는 최소화).
 */
function AppShell() {
  const { theme } = useApp();

  return (
    <div className={`app ${theme}`}>
      <header>
        <h1>useContext 예제</h1>
        <span className="badge">상태는 AppContext · useApp()</span>
      </header>

      <Toolbar />
      <ContentPanel />
    </div>
  );
}

export default function App() {
  return (
    <AppProvider>
      <AppShell />
    </AppProvider>
  );
}

 

components/ContnetPanel.jax

import { useApp } from '../context/AppContext';

/**
 * ContentPanel — 역시 useApp()으로 theme, count를 읽습니다.
 */
function ContentPanel() {
  const { theme, count } = useApp();

  return (
    <section className="panel">
      <p className="note">
        <strong>ContentPanel</strong>도 <code>useApp()</code>으로 동일한 값을 구독합니다.
      </p>
      <p>현재 테마 문자열: <code>{theme}</code></p>
      <p className="counter">{count}</p>
    </section>
  );
}

export default ContentPanel;



components/Toobar.jsx

import { useApp } from '../context/AppContext';

/**
 * Toolbar — props 없이 useApp()으로 theme, count, 액션을 가져옵니다.
 */
function Toolbar() {
  const { theme, count, toggleTheme, increment } = useApp();

  return (
    <section className="panel">
      <p className="note">
        <strong>Toolbar</strong>는 props 없이 <code>useApp()</code>만 사용합니다.
      </p>
      <button type="button" className="btn-toggle" onClick={toggleTheme}>
        테마: {theme === 'light' ? '라이트' : '다크'} (전환)
      </button>
      <button type="button" className="btn-toggle" onClick={increment} style={{ marginLeft: '0.5rem' }}>
        카운트 +1 (현재 {count})
      </button>
    </section>
  );
}

export default Toolbar;

 

context/AppContext.jsx

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

const AppContext = createContext(null);

/**
 * App 전역 상태(theme, count)를 Context로 제공합니다.
 * 깊은 트리에서도 props drilling 없이 useApp()으로 접근합니다.
 */
export function AppProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const [count, setCount] = useState(0);

  const toggleTheme = () => {
    setTheme((t) => (t === 'light' ? 'dark' : 'light'));
  };

  const increment = () => setCount((c) => c + 1);

  const value = useMemo(
    () => ({
      theme,
      count,
      toggleTheme,
      increment,
    }),
    [theme, count]
  );

  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}

export function useApp() {
  const ctx = useContext(AppContext);
  if (!ctx) {
    throw new Error('useApp은 AppProvider 안에서만 사용하세요.');
  }
  return ctx;
}

 

index.css

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, sans-serif;
}

.app {
  min-height: 100vh;
  padding: 1.5rem;
  transition: background 0.2s, color 0.2s;
}

.app.light {
  background: #f8fafc;
  color: #0f172a;
}

.app.dark {
  background: #0f172a;
  color: #f1f5f9;
}

header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

header h1 {
  margin: 0;
  font-size: 1.25rem;
}

.badge {
  font-size: 0.75rem;
  padding: 0.25rem 0.5rem;
  border-radius: 6px;
  background: rgba(16, 185, 129, 0.2);
  color: #059669;
}

.app.dark .badge {
  background: rgba(52, 211, 153, 0.15);
  color: #6ee7b7;
}

.panel {
  border-radius: 12px;
  padding: 1.25rem;
  margin-bottom: 1rem;
  border: 1px solid rgba(148, 163, 184, 0.4);
}

.app.dark .panel {
  border-color: rgba(148, 163, 184, 0.25);
}

button {
  cursor: pointer;
  padding: 0.5rem 1rem;
  border-radius: 8px;
  border: none;
  font-weight: 600;
}

.btn-toggle {
  background: #059669;
  color: white;
}

.btn-toggle:hover {
  filter: brightness(1.05);
}

.counter {
  font-size: 2rem;
  font-weight: 700;
  margin: 0.5rem 0;
}

.note {
  font-size: 0.85rem;
  opacity: 0.85;
  line-height: 1.5;
}

 

'FrontEnd > React' 카테고리의 다른 글

React + TypeScript 시작 가이드 (Vite)  (0) 2026.03.29
props · useContex 비교 테마 바꾸기  (0) 2026.03.28
props - 테마바꾸기  (0) 2026.03.28
24차시. 프로젝트 정리  (0) 2026.03.24
23차시. 빌드 & 배포  (0) 2026.03.23