React 요약 8 - Context API
Updated:
Context 생성 및 사용
import { createContext, useContext, useState } from 'react';
// 1. Context 생성
const ThemeContext = createContext();
// 2. Provider 컴포넌트 생성
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
// Provider를 통해 값 제공
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. 커스텀 Hook으로 Context 사용 간소화
function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
// 4. Context 사용하는 컴포넌트
function ThemedButton() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
style={{
backgroundColor: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff',
border: `1px solid ${theme === 'light' ? '#333' : '#fff'}`,
padding: '8px 16px',
borderRadius: '4px'
}}
>
Toggle Theme
</button>
);
}
// 5. 앱에서 Provider 사용
function App() {
return (
<ThemeProvider>
<div className="app">
<h1>Context API Example</h1>
<ThemedButton />
<ThemedContent />
</div>
</ThemeProvider>
);
}
function ThemedContent() {
const { theme } = useTheme();
return (
<div
style={{
backgroundColor: theme === 'light' ? '#f8f9fa' : '#343a40',
color: theme === 'light' ? '#343a40' : '#f8f9fa',
padding: '20px',
margin: '20px 0',
borderRadius: '8px'
}}
>
<h2>Current Theme: {theme}</h2>
<p>This content changes based on the current theme.</p>
</div>
);
}
코멘트: Context API는 props drilling 없이 컴포넌트 트리 전체에 데이터를 전달하는 방법을 제공합니다. 주요 구성 요소는 다음과 같습니다:
createContext
: Context 객체 생성Context.Provider
: 자식 컴포넌트에 값 제공useContext
: 컴포넌트에서 Context 값 사용커스텀 Hook을 만들어 Context 사용을 간소화하는 것이 좋은 패턴입니다. 이렇게 하면 Context 사용 로직을 캡슐화하고 오류 처리를 추가할 수 있습니다.
여러 Context 조합하기
import { createContext, useContext, useState } from 'react';
// 여러 Context 생성
const ThemeContext = createContext();
const UserContext = createContext();
const LanguageContext = createContext();
// 각 Provider 컴포넌트
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => setTheme(prev => prev === 'light' ? 'dark' : 'light');
return (
<ThemeContext.Provider value=>
{children}
</ThemeContext.Provider>
);
}
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const login = (userData) => setUser(userData);
const logout = () => setUser(null);
return (
<UserContext.Provider value=>
{children}
</UserContext.Provider>
);
}
function LanguageProvider({ children }) {
const [language, setLanguage] = useState('en');
const changeLanguage = (lang) => setLanguage(lang);
return (
<LanguageContext.Provider value=>
{children}
</LanguageContext.Provider>
);
}
// 커스텀 Hooks
function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
function useUser() {
const context = useContext(UserContext);
if (context === undefined) {
throw new Error('useUser must be used within a UserProvider');
}
return context;
}
function useLanguage() {
const context = useContext(LanguageContext);
if (context === undefined) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
}
// 모든 Provider를 결합한 컴포넌트
function AppProviders({ children }) {
return (
<ThemeProvider>
<UserProvider>
<LanguageProvider>
{children}
</LanguageProvider>
</UserProvider>
</ThemeProvider>
);
}
// 앱에서 사용
function App() {
return (
<AppProviders>
<Dashboard />
</AppProviders>
);
}
// 여러 Context를 사용하는 컴포넌트
function Dashboard() {
const { theme, toggleTheme } = useTheme();
const { user, logout } = useUser();
const { language, changeLanguage } = useLanguage();
return (
<div className={`dashboard ${theme}`}>
<header>
<h1>
{language === 'en' ? 'Dashboard' : '대시보드'}
</h1>
<div className="controls">
<button onClick={toggleTheme}>
{language === 'en' ? 'Toggle Theme' : '테마 변경'}
</button>
<select
value={language}
onChange={(e) => changeLanguage(e.target.value)}
>
<option value="en">English</option>
<option value="ko">한국어</option>
</select>
{user && (
<button onClick={logout}>
{language === 'en' ? 'Logout' : '로그아웃'}
</button>
)}
</div>
</header>
<main>
{user ? (
<p>
{language === 'en'
? `Welcome, ${user.name}!`
: `환영합니다, ${user.name}님!`}
</p>
) : (
<LoginForm />
)}
</main>
</div>
);
}
function LoginForm() {
const { login } = useUser();
const { language } = useLanguage();
const handleSubmit = (e) => {
e.preventDefault();
// 실제로는 API 호출 등을 통해 인증
login({ id: 1, name: 'John Doe' });
};
return (
<form onSubmit={handleSubmit}>
<h2>{language === 'en' ? 'Login' : '로그인'}</h2>
<button type="submit">
{language === 'en' ? 'Sign In' : '로그인'}
</button>
</form>
);
}
코멘트: 여러 Context를 조합할 때는 각 Context를 별도의 Provider로 분리하고, 이를 결합하는 컴포넌트(AppProviders)를 만드는 것이 좋습니다. 이렇게 하면 관심사를 분리하고 코드를 모듈화할 수 있습니다. 각 Context에 대한 커스텀 Hook을 만들면 사용이 더 간편해집니다.
Context 성능 최적화
import { createContext, useContext, useState, useMemo, useCallback } from 'react';
// Context 생성
const CounterContext = createContext();
function CounterProvider({ children }) {
const [count, setCount] = useState(0);
// useCallback으로 함수 메모이제이션
const increment = useCallback(() => {
setCount(c => c + 1);
}, []);
const decrement = useCallback(() => {
setCount(c => c - 1);
}, []);
const reset = useCallback(() => {
setCount(0);
}, []);
// useMemo로 Context 값 메모이제이션
const value = useMemo(() => ({
count,
increment,
decrement,
reset
}), [count, increment, decrement, reset]);
return (
<CounterContext.Provider value={value}>
{children}
</CounterContext.Provider>
);
}
// 커스텀 Hook
function useCounter() {
const context = useContext(CounterContext);
if (context === undefined) {
throw new Error('useCounter must be used within a CounterProvider');
}
return context;
}
// Context 값 일부만 사용하는 컴포넌트
function CountDisplay() {
// count만 사용하고 함수는 사용하지 않음
const { count } = useCounter();
console.log('CountDisplay rendered');
return <div>Count: {count}</div>;
}
// 다른 Context 값을 사용하는 컴포넌트
function CountButtons() {
// 함수만 사용하고 count는 사용하지 않음
const { increment, decrement, reset } = useCounter();
console.log('CountButtons rendered');
return (
<div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}
// Context 분할 예시
const CountStateContext = createContext();
const CountActionsContext = createContext();
function OptimizedCounterProvider({ children }) {
const [count, setCount] = useState(0);
const increment = useCallback(() => setCount(c => c + 1), []);
const decrement = useCallback(() => setCount(c => c - 1), []);
const reset = useCallback(() => setCount(0), []);
const actions = useMemo(() => ({
increment, decrement, reset
}), [increment, decrement, reset]);
return (
<CountStateContext.Provider value={count}>
<CountActionsContext.Provider value={actions}>
{children}
</CountActionsContext.Provider>
</CountStateContext.Provider>
);
}
function useCountState() {
const context = useContext(CountStateContext);
if (context === undefined) {
throw new Error('useCountState must be used within a OptimizedCounterProvider');
}
return context;
}
function useCountActions() {
const context = useContext(CountActionsContext);
if (context === undefined) {
throw new Error('useCountActions must be used within a OptimizedCounterProvider');
}
return context;
}
// 최적화된 컴포넌트
function OptimizedCountDisplay() {
// 상태만 구독
const count = useCountState();
console.log('OptimizedCountDisplay rendered');
return <div>Count: {count}</div>;
}
function OptimizedCountButtons() {
// 액션만 구독
const { increment, decrement, reset } = useCountActions();
console.log('OptimizedCountButtons rendered');
return (
<div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}
코멘트: Context를 사용할 때 성능 최적화를 위한 몇 가지 전략이 있습니다:
useMemo
와useCallback
을 사용하여 Context 값과 함수를 메모이제이션- Context를 분할하여 상태와 액션을 별도로 관리 (Context 분할 패턴)
- 컴포넌트가 필요한 Context 값만 구독하도록 설계
Context 값이 자주 변경되는 경우, 모든 소비자 컴포넌트가 리렌더링될 수 있으므로 이러한 최적화가 중요합니다. 특히 큰 애플리케이션에서는 Context를 논리적 단위로 분할하는 것이 좋습니다.
Leave a comment