
import React, { createContext, useState, useContext, useMemo } from 'react'; // useMemo 추가
// 1. Context 생성 (기본값 설정 가능)
interface UserContextType {
username: string | null;
login: (name: string) => void;
logout: () => void;
}
const UserContext = createContext<UserContextType | undefined>(undefined);
// 2. Provider 컴포넌트 생성 (상태와 상태 변경 함수 제공)
export function UserProvider({ children }: { children: React.ReactNode }) {
const [username, setUsername] = useState<string | null>(null);
const login = (name: string) => setUsername(name);
const logout = () => setUsername(null);
// value 객체 메모이제이션 적용
const value = useMemo(() => ({ username, login, logout }), [username]);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
// 3. useContext 훅으로 Context 값 사용
export function useUser() {
const context = useContext(UserContext);
if (context === undefined) {
throw new Error('useUser must be used within a UserProvider');
}
return context;
}
// 사용 예시 컴포넌트
function UserProfile() {
const { username, logout } = useUser();
return username ? (
<div>
<span>안녕하세요, {username}님!</span>
<button onClick={logout}>로그아웃</button>
</div>
) : (
<span>로그인이 필요합니다.</span>
);
}
// 앱 최상단 등에서 Provider로 감싸기
function App() {
return (
<UserProvider>
{/* ... 다른 컴포넌트들 ... */}
<UserProfile />
</UserProvider>
);
}
// 예시: 상태와 디스패치 함수 분리
const UserStateContext = createContext<string | null>(null);
const UserDispatchContext = createContext<{ login: (name: string) => void; logout: () => void } | undefined>(undefined);
// Provider에서 두 Context 제공
// 사용하는 컴포넌트는 필요한 Context만 구독
// function UserProfile() { const username = useContext(UserStateContext); ... }
// function LoginButton() { const { login } = useContext(UserDispatchContext); ... }Q:「Context APIの性能問題と最適化方法は何ですか。」 A:「valueの変更で全Consumerが不要に再描画する場合があります。useMemo、Contextの分離、読み取りと更新関数の分離などで最適化できます。」
// Redux Toolkit 예시 (Slice 정의)
import { createSlice, configureStore, PayloadAction } from '@reduxjs/toolkit';
import { useSelector, useDispatch } from 'react-redux'; // useSelector, useDispatch 추가
interface CounterState {
value: number;
}
const initialState: CounterState = { value: 0 };
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: state => { state.value += 1; }, // immer 내장으로 불변성 관리 용이
decrement: state => { state.value -= 1; },
incrementByAmount: (state, action: PayloadAction<number>) => { // 액션 페이로드 타입 지정
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 스토어 설정
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
// RootState 타입 정의 (스토어 상태 전체 타입)
export type RootState = ReturnType<typeof store.getState>;
// AppDispatch 타입 정의 (디스패치 함수 타입)
export type AppDispatch = typeof store.dispatch;
// 컴포넌트에서 사용
function CounterComponent() {
// useSelector 사용 시 RootState 타입 지정
const count = useSelector((state: RootState) => state.counter.value);
// useDispatch 사용 시 AppDispatch 타입 사용 (선택적이지만 권장)
const dispatch: AppDispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>증가</button>
<button onClick={() => dispatch(decrement())}>감소</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5 증가</button>
</div>
);
}shallowなどで比較もカスタマイズできます。// Zustand 예시 (Store 생성)
import create from 'zustand';
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
}
const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
// 컴포넌트에서 사용
function CounterComponent() {
// 필요한 상태와 액션만 선택하여 구독 (렌더링 최적화)
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
const decrement = useCounterStore((state) => state.decrement);
// 또는 아래처럼 객체로 받아올 경우, count가 변하지 않아도 다른 상태 변경 시 리렌더링 될 수 있음 (shallow 옵션 고려)
// const { count, increment, decrement } = useCounterStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>증가</button>
<button onClick={decrement}>감소</button>
</div>
);
}// Recoil 예시 (Atom 정의)
import React from 'react'; // React 임포트 추가
import { RecoilRoot, atom, selector, useRecoilState, useRecoilValue } from 'recoil'; // RecoilRoot 추가
const counterState = atom({
key: 'counterState', // 고유한 ID
default: 0,
});
// 파생 상태 예시 (Selector)
const doubledCounterState = selector({
key: 'doubledCounterState',
get: ({ get }) => {
const count = get(counterState);
return count * 2;
},
});
// 컴포넌트에서 사용
function CounterComponent() {
const [count, setCount] = useRecoilState(counterState); // 상태 값과 setter 함수
const doubledCount = useRecoilValue(doubledCounterState); // 읽기 전용 값
return (
<div>
<p>Count: {count}</p>
<p>Doubled Count: {doubledCount}</p>
<button onClick={() => setCount(prev => prev + 1)}>증가</button> {/* 함수형 업데이트 권장 */}
<button onClick={() => setCount(prev => prev - 1)}>감소</button>
</div>
);
}
// 앱 최상단 등에서 RecoilRoot로 감싸기
function AppRecoil() {
return (
<RecoilRoot>
<CounterComponent />
</RecoilRoot>
);
}Q:「Redux、Zustand、Recoilなどの特徴と長所・短所を比べてください。」 A:それぞれの思想、長所と短所、代表的な利用場面を説明します。
| ライブラリ | 特徴 | 長所 | 短所 |
|---|---|---|---|
Context API | React組み込みの共有状態管理 | 追加インストール不要 単純な構造 | 不要な再描画が起こりやすく最適化が難しい 大規模では非効率 |
Redux Toolkit | Redux公式推奨 予測可能 | 強力なDevTools 非同期処理やミドルウェアに強い | コードが冗長になりうる 学習コスト |
Zustand | 単純で柔軟な軽量管理 Flux不要 | 直感的 必要部分だけ再描画 | 大規模の設計に注意 DevToolsは限定的 |
Recoil | AtomとSelector 非同期処理を内蔵 | 細かな状態分離 非同期管理が便利 | 完全な安定性はまだ やや大きいライブラリ |

useSelector(state => state.user.name)は、user.nameだけを取得します。ストアはツリーやハッシュテーブル・Map、購読者は配列や連結リストで管理できます。Selectorのメモ化にはキャッシュを利用します。
// 예시: API 호출 함수 (가정)
async function fetchTodos(status?: string) {
const url = status ? `/api/todos?status=${status}` : '/api/todos';
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
// React Query 예시
import { useQuery } from 'react-query'; // TanStack Query v4 기준, v5는 @tanstack/react-query
// 키: ['todos', { status: 'completed' }] -> 쿼리 함수에 객체로 전달됨
const { data, isLoading, error } = useQuery(
['todos', { status: 'completed' }],
() => fetchTodos('completed') // 쿼리 함수는 Promise를 반환해야 함
);
// SWR 예시
import useSWR from 'swr';
// fetcher 함수 정의 (SWR은 키를 fetcher의 첫 인자로 전달)
const fetcher = (url: string) => fetch(url).then(res => {
if (!res.ok) throw new Error('An error occurred while fetching the data.');
return res.json();
});
// 키: '/api/todos?status=completed' -> fetcher 함수에 url로 전달됨
const { data: swrData, error: swrError } = useSWR('/api/todos?status=completed', fetcher);
次回はReact Routerのルーティングを扱います。要求に応じた表示、入れ子のルート、コード分割などを確認します。