본문 바로가기

Front-End/React

React #26 (Zustand)

반응형

 

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가 주류로 되어 있어서... 크아아악 ㅠㅠ

 

 

반응형