반응형
https://chiro-j.tistory.com/90
앞에서 (React #25) 했던 실습 예제들을 Zustand로 바꿔보자
※ Zustand 패키지 설치
npm install zustand
아무래도 store 쪽에서 정의하고 hooks에서 커스텀 훅을 만들어서 index.tsx 쪽에서 Provider로 묶어서 쓰는 게 아니라,
아예 그냥 store에서 useOOO으로 커스텀 훅처럼 하나에 만들어서 꺼내 쓰다보니 Provider로 묶을 필요도 없어서,
폴더 디렉토리 구조가 훨씬 더 깔끔해졌다.
src/
├── components/
│ ├── Counter.tsx
│ └── TodoList.tsx
├── pages/
├── data/
├── hooks/
├── context/
├── store/
│ ├── useCounterStore.ts
│ └── useTodoStore.ts
├── types/
│ ├── counter.ts
│ └── todo.ts
├── App.tsx
└── index.tsx
※ 위에서 말했지만, index.tsx에서 따로 손대지 말 것. (초기 설정 당시 코드 그대로 두기)
더보기
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import "@fontsource/material-icons";
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
1. Counter
더보기
1. 타입 정의 ( src/types/counter.ts )
export interface CounterState {
value: number;
increment: () => void;
decrement: () => void;
incrementByAmount: (amount: number) => void;
reset: () => void;
}
2. zustand 스토어 정의 ( src/store/useCounterStore.ts )
import { create } from "zustand";
import type { CounterState } from "../types/counter";
export const useCounterStore = create<CounterState>((set) => ({
value: 0,
increment: () => set((state) => ({ value: state.value + 1 })),
decrement: () => set((state) => ({ value: state.value - 1 })),
incrementByAmount: (amount) => set((state) => ({ value: state.value + amount })),
reset: () => set(({ value: 0})),
}))
3. Counter 컴포넌트 ( src/components/Counter.tsx )
import React from "react";
import { useCounterStore } from "../store/useCounterStore";
const Counter: React.FC = () => {
const value = useCounterStore((state) => state.value);
const increment = useCounterStore((state) => state.increment);
const decrement = useCounterStore((state) => state.decrement);
const incrementByAmount = useCounterStore((state) => state.incrementByAmount);
const reset = useCounterStore((state) => state.reset);
return (
<div>
<h2>Counter: {value}</h2>
<button className="ml-4" onClick={increment}> [+ 1] </button>
<button className="ml-4" onClick={decrement}> [- 1] </button>
<button className="ml-4" onClick={() => incrementByAmount(5)}> [+ 5] </button>
<button className="ml-4" onClick={reset}> [Reset] </button>
</div>
)
}
export default Counter;
4. App.tsx ( src/App.tsx )
import React from 'react';
import './App.css';
import Counter from './components/Counter';
function App() {
return (
<div className="App">
<h1>Zustand 실습</h1>
<Counter />
<hr />
</div>
);
}
export default App;
실습한 코드들은 아래 페이지에서 확인 가능
https://codesandbox.io/p/sandbox/zustand-practice-1-forked-gpqslz
1. TodoList
더보기
1. 타입 정의 ( src/types/todo.ts )
export interface Todo {
id: number;
text: string;
done: boolean;
}
export interface TodoState {
todos: Todo[];
nextId: number;
addTodo: (text: string) => void;
toggleTodo: (id: number) => void;
removeTodo: (id: number) => void;
clearTodos: () => void;
}
2. zustand 스토어 정의 ( src/store/useTodoStore.ts )
import { create } from "zustand";
import type { TodoState } from "../types/todo";
export const useTodoStore = create<TodoState>((set) => ({
todos: [],
nextId: 1,
addTodo: (text) =>
set((state) => ({
todos: [...state.todos, { id: state.nextId, text, done: false }],
nextId: state.nextId + 1,
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, done: !todo.done } : todo
),
})),
removeTodo: (id) =>
set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id),
})),
clearTodos: () => set({ todos: [], nextId: 1 }),
}))
3. TodoList 컴포넌트 ( src/components/TodoList.tsx )
import React, { useState } from "react";
import { useTodoStore } from "../store/useTodoStore";
const TodoList: React.FC = () => {
const [input, setInput] = useState('');
const todos = useTodoStore((state) => state.todos);
const addTodo = useTodoStore((state) => state.addTodo);
const toggleTodo = useTodoStore((state) => state.toggleTodo);
const removeTodo = useTodoStore((state) => state.removeTodo);
const clearTodo = useTodoStore((state) => state.clearTodos);
const handleAdd = () => {
if (input.trim() !== '') {
addTodo(input);
setInput('');
}
};
return (
<div>
<h2>Todo List</h2>
<div>
<input value={input} onChange={(e) => setInput(e.target.value)} placeholder="할 일을 입력하세요" />
<button onClick={handleAdd}> 추가 </button>
<button onClick={clearTodo}> 전체 삭제 </button>
</div>
<ul>
{todos.map((todo) => (
<li key={todo.id} style={{ textDecoration: todo.done ? 'line-through': 'none' }}>
<span onClick={() => toggleTodo(todo.id)} style={{ cursor: 'pointer' }} >
{todo.text}
</span>
<button onClick={() => removeTodo(todo.id)}> 삭제 </button>
</li>
))}
</ul>
</div>
)
}
export default TodoList;
4. App.tsx ( src/App.tsx )
import React from 'react';
import './App.css';
import TodoList from './components/TodoList';
function App() {
return (
<div className="App">
<h1>Zustand 실습</h1>
<TodoList />
<hr />
</div>
);
}
export default App;
실습한 코드들은 아래 페이지에서 확인 가능
https://codesandbox.io/p/sandbox/white-browser-9xg6yd
Zustand 그는 신이야...!
마치 빗자루 청소에서 진공 청소기, 그리고 그 다음으로 로봇 청소기 레벨까지 온 것 같다...
근데 대부분 redux가 주류로 되어 있어서... 크아아악 ㅠㅠ
반응형
'Front-End > React' 카테고리의 다른 글
| React #25 (Redux toolkit - Slice + 실습 예제) (0) | 2025.07.23 |
|---|---|
| React #24 (Redux - 리듀서 활용) (1) | 2025.07.22 |
| React #23 (훅, 상태관리 - 내장 useReducer vs Redux 패키지) (1) | 2025.07.22 |
| React #22 (api 통신 - fetch, promise) (0) | 2025.07.22 |
| React #21 (고급 훅 - useId, useTransition, useImperativeHandle) (1) | 2025.07.21 |