state-management

安装量: 10.4K
排名: #229

安装

npx skills add https://github.com/supercent-io/skills-template --skill state-management

State Management When to use this skill 전역 상태 필요: 여러 컴포넌트가 같은 데이터 공유 Props Drilling 문제: 5단계 이상 props 전달 복잡한 상태 로직: 인증, 장바구니, 테마 등 상태 동기화: 서버 데이터와 클라이언트 상태 동기화 Instructions Step 1: 상태 범위 결정

로컬 vs 전역 상태를 구분합니다.

판단 기준:

로컬 상태: 단일 컴포넌트에서만 사용

폼 입력값, 토글 상태, 드롭다운 열림/닫힘 useState, useReducer 사용

전역 상태: 여러 컴포넌트에서 공유

사용자 인증, 장바구니, 테마, 언어 설정 Context API, Redux, Zustand 사용

예시:

// ✅ 로컬 상태 (단일 컴포넌트) function SearchBox() { const [query, setQuery] = useState(''); const [isOpen, setIsOpen] = useState(false);

return (

setQuery(e.target.value)} onFocus={() => setIsOpen(true)} /> {isOpen && }
); }

// ✅ 전역 상태 (여러 컴포넌트) // 사용자 인증 정보는 Header, Profile, Settings 등에서 사용 const { user, logout } = useAuth(); // Context 또는 Zustand

Step 2: React Context API (간단한 전역 상태)

가벼운 전역 상태 관리에 적합합니다.

예시 (인증 Context):

// contexts/AuthContext.tsx import { createContext, useContext, useState, ReactNode } from 'react';

interface User { id: string; email: string; name: string; }

interface AuthContextType { user: User | null; login: (email: string, password: string) => Promise; logout: () => void; isAuthenticated: boolean; }

const AuthContext = createContext(undefined);

export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null);

const login = async (email: string, password: string) => { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) });

const data = await response.json();
setUser(data.user);
localStorage.setItem('token', data.token);

};

const logout = () => { setUser(null); localStorage.removeItem('token'); };

return ( {children} ); }

// Custom hook export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within AuthProvider'); } return context; }

사용:

// App.tsx function App() { return (

); }

// Header.tsx function Header() { const { user, logout, isAuthenticated } = useAuth();

return (

{isAuthenticated ? ( <> Welcome, {user!.name} </> ) : ( Login )}
); }

Step 3: Zustand (현대적이고 간결한 상태 관리)

Redux보다 간단하고 보일러플레이트가 적습니다.

설치:

npm install zustand

예시 (장바구니):

// stores/cartStore.ts import { create } from 'zustand'; import { devtools, persist } from 'zustand/middleware';

interface CartItem { id: string; name: string; price: number; quantity: number; }

interface CartStore { items: CartItem[]; addItem: (item: Omit) => void; removeItem: (id: string) => void; updateQuantity: (id: string, quantity: number) => void; clearCart: () => void; total: () => number; }

export const useCartStore = create()( devtools( persist( (set, get) => ({ items: [],

    addItem: (item) => set((state) => {
      const existing = state.items.find(i => i.id === item.id);
      if (existing) {
        return {
          items: state.items.map(i =>
            i.id === item.id
              ? { ...i, quantity: i.quantity + 1 }
              : i
          )
        };
      }
      return { items: [...state.items, { ...item, quantity: 1 }] };
    }),

    removeItem: (id) => set((state) => ({
      items: state.items.filter(item => item.id !== id)
    })),

    updateQuantity: (id, quantity) => set((state) => ({
      items: state.items.map(item =>
        item.id === id ? { ...item, quantity } : item
      )
    })),

    clearCart: () => set({ items: [] }),

    total: () => {
      const { items } = get();
      return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
    }
  }),
  { name: 'cart-storage' }  // localStorage key
)

) );

사용:

// components/ProductCard.tsx function ProductCard({ product }) { const addItem = useCartStore(state => state.addItem);

return (

{product.name}

${product.price}

); }

// components/Cart.tsx function Cart() { const items = useCartStore(state => state.items); const total = useCartStore(state => state.total()); const removeItem = useCartStore(state => state.removeItem);

return (

Cart

{items.map(item => (
{item.name} x {item.quantity} ${item.price * item.quantity}
))}

Total: ${total.toFixed(2)}

); }

Step 4: Redux Toolkit (대규모 앱)

복잡한 상태 로직과 미들웨어가 필요한 경우 사용합니다.

설치:

npm install @reduxjs/toolkit react-redux

예시 (Todo):

// store/todosSlice.ts import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';

interface Todo { id: string; text: string; completed: boolean; }

interface TodosState { items: Todo[]; status: 'idle' | 'loading' | 'failed'; }

const initialState: TodosState = { items: [], status: 'idle' };

// 비동기 액션 export const fetchTodos = createAsyncThunk('todos/fetch', async () => { const response = await fetch('/api/todos'); return response.json(); });

const todosSlice = createSlice({ name: 'todos', initialState, reducers: { addTodo: (state, action: PayloadAction) => { state.items.push({ id: Date.now().toString(), text: action.payload, completed: false }); }, toggleTodo: (state, action: PayloadAction) => { const todo = state.items.find(t => t.id === action.payload); if (todo) { todo.completed = !todo.completed; } }, removeTodo: (state, action: PayloadAction) => { state.items = state.items.filter(t => t.id !== action.payload); } }, extraReducers: (builder) => { builder .addCase(fetchTodos.pending, (state) => { state.status = 'loading'; }) .addCase(fetchTodos.fulfilled, (state, action) => { state.status = 'idle'; state.items = action.payload; }) .addCase(fetchTodos.rejected, (state) => { state.status = 'failed'; }); } });

export const { addTodo, toggleTodo, removeTodo } = todosSlice.actions; export default todosSlice.reducer;

// store/index.ts import { configureStore } from '@reduxjs/toolkit'; import todosReducer from './todosSlice';

export const store = configureStore({ reducer: { todos: todosReducer } });

export type RootState = ReturnType; export type AppDispatch = typeof store.dispatch;

사용:

// App.tsx import { Provider } from 'react-redux'; import { store } from './store';

function App() { return ( ); }

// components/TodoList.tsx import { useSelector, useDispatch } from 'react-redux'; import { RootState } from '../store'; import { toggleTodo, removeTodo } from '../store/todosSlice';

function TodoList() { const todos = useSelector((state: RootState) => state.todos.items); const dispatch = useDispatch();

return (

    {todos.map(todo => (
  • dispatch(toggleTodo(todo.id))} /> {todo.text}
  • ))}
); }

Step 5: 서버 상태 관리 (React Query / TanStack Query)

API 데이터 fetching 및 캐싱에 특화되어 있습니다.

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

function UserProfile({ userId }: { userId: string }) { const queryClient = useQueryClient();

// GET: 사용자 정보 조회 const { data: user, isLoading, error } = useQuery({ queryKey: ['user', userId], queryFn: async () => { const res = await fetch(/api/users/${userId}); return res.json(); }, staleTime: 5 * 60 * 1000, // 5분간 캐시 });

// POST: 사용자 정보 수정 const mutation = useMutation({ mutationFn: async (updatedUser: Partial) => { const res = await fetch(/api/users/${userId}, { method: 'PATCH', body: JSON.stringify(updatedUser) }); return res.json(); }, onSuccess: () => { // 캐시 무효화 및 재조회 queryClient.invalidateQueries({ queryKey: ['user', userId] }); } });

if (isLoading) return

Loading...
; if (error) return
Error: {error.message}
;

return (

{user.name}

{user.email}

); }

Output format 상태 관리 도구 선택 가이드 상황별 추천 도구:

  1. 간단한 전역 상태 (테마, 언어) → React Context API

  2. 중간 복잡도 (장바구니, 사용자 설정) → Zustand

  3. 대규모 앱, 복잡한 로직, 미들웨어 필요 → Redux Toolkit

  4. 서버 데이터 fetching/caching → React Query (TanStack Query)

  5. 폼 상태 → React Hook Form + Zod

Constraints 필수 규칙 (MUST)

상태 불변성: 상태는 절대 직접 수정하지 않음

// ❌ 나쁜 예 state.items.push(newItem);

// ✅ 좋은 예 setState({ items: [...state.items, newItem] });

최소 상태 원칙: 파생 가능한 값은 상태로 저장하지 않음

// ❌ 나쁜 예 const [items, setItems] = useState([]); const [count, setCount] = useState(0); // items.length로 계산 가능

// ✅ 좋은 예 const [items, setItems] = useState([]); const count = items.length; // 파생 값

단일 진실의 원천: 같은 데이터를 여러 곳에 중복 저장 금지

금지 사항 (MUST NOT)

Props Drilling 과다: 5단계 이상 props 전달 금지

Context 또는 상태 관리 라이브러리 사용

모든 것을 전역 상태로: 로컬 상태로 충분한 경우 전역 상태 사용 지양

Best practices

선택적 구독: 필요한 상태만 구독

// ✅ 좋은 예: 필요한 것만 const items = useCartStore(state => state.items);

// ❌ 나쁜 예: 전체 구독 const { items, addItem, removeItem, updateQuantity, clearCart } = useCartStore();

액션 이름 명확히: update → updateUserProfile

TypeScript 사용: 타입 안정성 확보

References Zustand Redux Toolkit React Query Jotai Recoil Metadata 버전 현재 버전: 1.0.0 최종 업데이트: 2025-01-01 호환 플랫폼: Claude, ChatGPT, Gemini 관련 스킬 ui-component-patterns: 컴포넌트와 상태 통합 backend-testing: 상태 로직 테스트 태그

state-management #React #Redux #Zustand #Context #global-state #frontend

Examples Example 1: Basic usage Example 2: Advanced usage

返回排行榜