공식 문서

Zustand Documentation

 

Zustand Documentation

Zustand is a small, fast and scalable bearbones state-management solution, it has a comfy api based on hooks

docs.pmnd.rs

설치하기

npm install zustand

 

스토어 생성하기

import { create } from 'zustand'

const useStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
  updateBears: (newBears) => set({ bears: newBears }),
}))

create

스토어를 생성하기 위한 함수

 

set

set 함수는 상태를 업데이트하기 위해 사용되는 함수

Zustand 스토어를 생성할 때 create 함수에 전달되는 콜백 함수는 set 함수를 인자로 받음

이 set 함수를 사용하여 스토어의 상태를 동기적으로 업데이트할 수 있음

 

useStore의 상태(state) 객체 내의 키

increasePopulation, removeAllBears, updateBears이 함수들은 상태를 변화시키는 액션(action)이다. 이들 각각은 상태를 변경하는 함수를 정의한다.

각 함수는 상태를 업데이트하는 로직을 포함하고 있으며, set 함수를 통해 상태를 비동기적으로 업데이트합니다.

 

스토어 사용하기

훅 사용

BearCounterControls,는 useStore 훅을 사용하여 Zustand 상태 관리 라이브러리로부터 상태와 액션을 가져와서 사용합니다.

function BearCounter() {
  const bears = useStore((state) => state.bears)
  return <h1>{bears} around here...</h1>
}

function Controls() {
  const increasePopulation = useStore((state) => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}

 

예제

기본 TODO 리스트 만들기

//stote.ts

import { create } from "zustand";

interface ITodo {
    id: string;
    message: string;
}

interface ITodoList {
    todos: ITodo[];
    addTodo: (todo: ITodo) => void;
    removeTodo: (id: string) => void;
}

const useStore = create<ITodoList>((set) => ({
    todos: [],
    addTodo: (todo) =>
        set((prevState) => ({
            todos: [...prevState.todos, todo],
        })),
    removeTodo: (id) =>
        set((prevState) => {
            // todos: prevState.todos.filter((todo) => todo.id !== id),
            const newTodos = prevState.todos.filter((todo) => todo.id !== id);
            return {
                todos: newTodos,
            };
        }),
}));

export default useStore;

//App.ts

import React, { useState } from "react";
import { v4 as uuidv4 } from "uuid";

import useStore from "./store/store";

function App() {
    const [value, setValue] = useState<string>("");

    const todoList = useStore((state) => state.todos);

    const addTodo = useStore((state) => state.addTodo);
    const removeTodo = useStore((state) => state.removeTodo);

    const handleChange = (e) => {
        const value = e.target.value;
        setValue(value);
    };

    const handleClickSave = () => {
        addTodo({
            id: uuidv4(),
            message: value,
        });
        setValue("");
    };

    const handleClickRemove = (id: string) => {
        removeTodo(id);
    };

    return (
        <div>
            <input
                value={value}
                onChange={handleChange}
                placeholder="Todo 입력"
            />
            <button onClick={handleClickSave}>저장</button>
            <ul>
                {todoList.map((todo) => (
                    <li key={todo.id} onClick={() => console.log()}>
                        {todo.message}{" "}
                        <button onClick={() => handleClickRemove(todo.id)}>
                            삭제
                        </button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default App;

 

리덕스 처럼 사용하기

Redux에서는 액션과 리듀서를 사용하여 상태 업데이트를 관리하는데, 리덕스 처럼 구현해보자.

 

액션(Action) : Redux에서 액션은 상태를 변화 시키고자 할 때 발송하는 정보의 패키지로, TodoActionType은 두 가지 타입의 액션(ADD_VIEWER, REMOVE_VIEWER)을 정의하고, 이에 필요한 데이터를 포함한다.

 

디스패치(Dispatch): 액션을 발생시키는 과정으로 dispatch 함수가 이 역할을 하며, set 함수와 함께 사용되어 상태를 비동기적으로 업데이트한다.

 

리듀서(Reducer): 액션을 받아 이전 상태를 다음 상태로 변환하는 순수 함수로, **addTodo**와 removeTodo 함수가 이러한 리듀서의 역할을 수행한다.

각 함수는 현재 상태와 액션을 기반으로 새로운 상태 객체를 생성하여 반환한다.

//stote.ts

import { create } from "zustand";

interface ITodo {
    id: string;
    message: string;
}

type TodoActionType =
    | { type: "ADD_TODO"; todo: ITodo }
    | { type: "REMOVE_TODO"; id: string };

interface ITodoList {
    todos: ITodo[];
    dispatch: (action: TodoActionType) => void;
}

// ADD_TODO 액션에 대한 처리 로직
function addTodo(state: ITodoList, action: { todo: ITodo }): ITodoList {
    return {
        ...state,
        todos: [...state.todos, action.todo],
    };
}

// REMOVE_TODO 액션에 대한 처리 로직
function removeTodo(state: ITodoList, action: { id: string }): ITodoList {
    return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.id),
    };
}

const useStore = create<ITodoList>((set) => ({
    todos: [],
    dispatch: (action) => {
        set((state) => {
            switch (action.type) {
                case "ADD_TODO":
                    return addTodo(state, action);
                case "REMOVE_TODO":
                    return removeTodo(state, action);
                default:
                    return state;
            }
        });
    },
}));

export default useStore;

//App.ts

import React, { useState } from "react";
import { v4 as uuidv4 } from "uuid";

import useStore from "./store/store";

function App() {
    const [value, setValue] = useState<string>("");

    const todoList = useStore((state) => state.todos);

    const dispatch = useStore((state) => state.dispatch);

    const handleChange = (e) => {
        const value = e.target.value;
        setValue(value);
    };

    const handleClickSave = () => {
        dispatch({
            type: "ADD_TODO",
            todo: {
                id: uuidv4(),
                message: value,
            },
        });
        setValue("");
    };

    const handleClickRemove = (id: string) => {
        dispatch({
            type: "REMOVE_TODO",
            id: id,
        });
    };

    return (
        <div>
            <input
                value={value}
                onChange={handleChange}
                placeholder="Todo 입력"
            />
            <button onClick={handleClickSave}>저장</button>
            <ul>
                {todoList.map((todo) => (
                    <li key={todo.id} onClick={() => console.log()}>
                        {todo.message}{" "}
                        <button onClick={() => handleClickRemove(todo.id)}>
                            삭제
                        </button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default App;

 

스토어 클래스로 만들어서 사용하기

//stote.ts

import { create } from "zustand";

export interface ITodo {
    id: string;
    message: string;
}

type TodoActionType =
    | { type: "ADD_TODO"; todo: ITodo }
    | { type: "REMOVE_TODO"; id: string };

interface ITodoList {
    todos: ITodo[];
    dispatch: (action: TodoActionType) => void;
}

// ADD_TODO 액션에 대한 처리 로직
function addTodo(state: ITodoList, action: { todo: ITodo }): ITodoList {
    return { ...state, todos: [...state.todos, action.todo] };
}

// REMOVE_TODO 액션에 대한 처리 로직
function removeTodo(state: ITodoList, action: { id: string }): ITodoList {
    return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.id),
    };
}

class TodoStore {
    private useStore = create<ITodoList>((set) => ({
        todos: [],
        dispatch: (action) => {
            set((state) => {
                switch (action.type) {
                    case "ADD_TODO":
                        return addTodo(state, action);
                    case "REMOVE_TODO":
                        return removeTodo(state, action);
                    default:
                        return state;
                }
            });
        },
    }));

    get state() {
        return this.useStore.getState();
    }

    subscribe(listener: (state: any) => void) {
        return this.useStore.subscribe(listener);
    }

    dispatch(action: TodoActionType) {
        this.useStore.getState().dispatch(action);
    }
}

const todoStore = new TodoStore();
export default todoStore;

//App.ts

import React, { useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";

import todoStore, { ITodo } from "./store/store";

function App() {
    const [value, setValue] = useState<string>("");
    const [todos, setTodos] = useState<ITodo[]>(todoStore.state.todos);

    const handleChange = (e) => {
        const value = e.target.value;
        setValue(value);
    };

    const handleClickSave = () => {
        todoStore.dispatch({
            type: "ADD_TODO",
            todo: { id: uuidv4(), message: value },
        });
        setValue("");
    };

    const handleClickRemove = (id: string) => {
        todoStore.dispatch({
            type: "REMOVE_TODO",
            id: id,
        });
    };

    useEffect(() => {
        //subscribe 메소드는 스토어의 상태가 변경될 때마다 setTodos를 호출한다.
        const unsubscribe = todoStore.subscribe((state) => {
            setTodos(state.todos);
        });

        return () => unsubscribe(); // 컴포넌트 언마운트 시 구독 해제
    }, []);

    return (
        <div>
            <input
                value={value}
                onChange={handleChange}
                placeholder="Todo 입력"
            />
            <button onClick={handleClickSave}>저장</button>
            <ul>
                {todos.map((todo) => (
                    <li key={todo.id} onClick={() => console.log()}>
                        {todo.message}{" "}
                        <button onClick={() => handleClickRemove(todo.id)}>
                            삭제
                        </button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default App;

상태관리 라이브러리 zustand, 리덕스처럼 사용, 클래스로 만들어서 사용