1. 패키지 설치
npm install zustand
# 또는
yarn add zustand
2. 스토어(Store) 정의 파일 (src/store/useStore.js)
import { create } from 'zustand';
// create 함수를 이용해 스토어를 생성합니다.
// set: 상태를 업데이트(병합)하는 함수
// get: 현재 스토어의 상태를 조회할 수 있는 함수
export const useStore = create((set, get) => ({
// ==============================
// 1. 카운터 관련 상태 및 액션
// ==============================
count: 0,
// 1씩 증가시키는 액션
increase: () => set((state) => ({ count: state.count + 1 })),
// 1씩 감소시키는 액션
decrease: () => set((state) => ({ count: state.count - 1 })),
// 특정 값만큼 증가시키는 액션 (인자 받기 가능)
increaseBy: (amount) => set((state) => ({ count: state.count + amount })),
// ==============================
// 2. 할 일(Todo) 목록 및 비동기 액션
// ==============================
todos: [],
isLoading: false,
error: null,
// 로컬에서 Todo 추가하기
addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{ id: Date.now(), text, completed: false }
]
})),
// Todo 완료 상태 토글하기
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
})),
// 비동기 API 호출 예시 (서버에서 할 일 목록을 가져오는 시뮬레이션)
fetchTodos: async () => {
// 로딩 시작, 이전 에러 초기화
set({ isLoading: true, error: null });
try {
// 1초 뒤에 데이터가 응답한다고 가정 (실제로는 fetch나 axios 사용)
await new Promise((resolve) => setTimeout(resolve, 1000));
const mockData = [
{ id: 1, text: 'Zustand 공부하기', completed: true },
{ id: 2, text: '리액트 컴포넌트 작성하기', completed: false },
];
// 성공 시 데이터 저장 및 로딩 종료
set({ todos: mockData, isLoading: false });
} catch (err) {
// 에러 발생 시 에러 메시지 저장 및 로딩 종료
set({ error: '데이터를 불러오는데 실패했습니다.', isLoading: false });
}
},
// ==============================
// 3. 계산된 상태(Getter/Selectors) 예시
// ==============================
// 완료되지 않은 할 일의 개수를 반환하는 함수
getUncompletedCount: () => {
// get()을 사용하여 현재 스토어의 state에 접근할 수 있습니다.
const state = get();
return state.todos.filter((todo) => !todo.completed).length;
},
}));
3. 컴포넌트 사용 예시 (src/App.jsx)
import React, { useState } from 'react';
import { useStore } from './store/useStore';
export default function App() {
// 1. 필요한 상태와 액션만 골라서(Destructuring) 가져옵니다.
// Zustand는 필요한 것만 콕 집어 가져오기 때문에 불필요한 리렌더링을 방지합니다.
const count = useStore((state) => state.count);
const increase = useStore((state) => state.increase);
const decrease = useStore((state) => state.decrease);
const increaseBy = useStore((state) => state.increaseBy);
const todos = useStore((state) => state.todos);
const isLoading = useStore((state) => state.isLoading);
const error = useStore((state) => state.error);
const addTodo = useStore((state) => state.addTodo);
const toggleTodo = useStore((state) => state.toggleTodo);
const fetchTodos = useStore((state) => state.fetchTodos);
const getUncompletedCount = useStore((state) => state.getUncompletedCount);
// 로컬 입력값 상태 (컴포넌트 내부 상태는 기존 방식대로 useState 사용 가능)
const [inputText, setInputText] = useState('');
const handleAddSubmit = (e) => {
e.preventDefault();
if (!inputText.trim()) return;
addTodo(inputText);
setInputText(''); // 입력창 초기화
};
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif', maxWidth: '600px', margin: '0 auto' }}>
<h1>🚀 Zustand 상태 관리 샘플</h1>
{/* ================= 카운터 섹션 ================= */}
<section style={{ marginBottom: '30px', padding: '15px', border: '1px solid #ccc', borderRadius: '8px' }}>
<h2>카운터: {count}</h2>
<div style={{ display: 'flex', gap: '10px' }}>
<button onClick={increase}>+ 1 증가</button>
<button onClick={decrease}>- 1 감소</button>
<button onClick={() => increaseBy(5)}>+ 5 한번에 증가</button>
</div>
</section>
{/* ================= 투두 리스트 섹션 ================= */}
<section style={{ padding: '15px', border: '1px solid #ccc', borderRadius: '8px' }}>
<h2>할 일 목록 (남은 할 일: {getUncompletedCount()}개)</h2>
{/* 비동기 데이터 가져오기 버튼 */}
<div style={{ marginBottom: '15px' }}>
<button onClick={fetchTodos} disabled={isLoading}>
{isLoading ? '로딩 중...' : '서버에서 Todo 불러오기 (비동기)'}
</button>
</div>
{/* 에러 메시지 출력 */}
{error && <p style={{ color: 'red' }}>{error}</p>}
{/* 새로운 Todo 입력 폼 */}
<form onSubmit={handleAddSubmit} style={{ marginBottom: '15px' }}>
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="새로운 할 일을 입력하세요..."
style={{ padding: '6px', width: '70%', marginRight: '8px' }}
/>
<button type="submit">추가</button>
</form>
{/* Todo 목록 렌더링 */}
<ul>
{todos.map((todo) => (
<li
key={todo.id}
onClick={() => toggleTodo(todo.id)}
style={{
cursor: 'pointer',
textDecoration: todo.completed ? 'line-through' : 'none',
color: todo.completed ? '#888' : '#000',
marginBottom: '6px',
}}
>
{todo.text} {todo.completed ? '✅' : '⏳'}
</li>
))}
</ul>
</section>
</div>
);
}
.
'Programming > React' 카테고리의 다른 글
| zustand 사용 예(ts) - 회원 정보 관리 - Persist 적용x (0) | 2026.10.16 |
|---|---|
| zustand 사용 예(ts) - 회원 정보 관리 - 기초적이고 심플한 개인정보(CRUD) 코드 샘플 (0) | 2026.10.16 |
| vite 으로 리액트 프로젝트 시작하기 (0) | 2026.05.19 |
| cra 로 생성한 프로젝트에 빌드시 콘솔로그 코드 모두 제거하기 (eject 사용안함) (0) | 2025.02.26 |
| react build (빌드) 시 .js.map 파일 생성 안되도록 처리 (0) | 2020.01.28 |