[React] Zustand 상태 관리 라이브러리

jungmin Lee·2023년 9월 30일
0

Zustand

Zustand는 독일어로 '상태'라는 뜻을 가지고 있으며, 상태 관리를 위한 라이브러리이다. Zustand는 Context API를 기반으로 하며, 상태를 전역으로 관리하고 상태 업데이트를 처리할 수 있다. Zustand는 상태를 불변성을 유지하면서 업데이트하고, 컴포넌트 간의 데이터 공유를 용이하게 만든다. React에 직접적으로 의존하지 않으므로, 자주 바뀌는 상태에 대해서도 직접적으로 제어할 수 있는 방법도 있다고 한다. 기존의 Redux에 비하면 적은 양의 코드로 작성할 수 있다.


Zustand 설치 및 사용방법

🔹Zustand 설치

npm 설치

npm install zustand

yarn 설치

yarn add zustand

🔹Zustand 사용방법

store.ts
create 함수를 사용하여 Zustand 스토어를 만들고, 상태와 set 함수를 사용하여 상태를 변경하는 액션을 정의하고, useTodoStore 훅을 내보내서 다른 리액트 컴포넌트에서 사용할 수 있다. 새로운 Todo를 추가하는 addTodo와 Todo를 삭제하는 removeTodo를 설정하였다.

import { create } from 'zustand';

interface Todo {
  id: number;
  text: string;
}

interface TodoStore {
  todos: Todo[];
  addTodo: (todo: Todo) => void;
  removeTodo: (todoId: number) => void;
}

const useTodoStore = create<TodoStore>((set) => ({
  todos: [],
  addTodo: (todo) => set((state) => ({ todos: [...state.todos, todo] })),
  removeTodo: (todoId) =>
    set((state) => ({
      todos: state.todos.filter((todo) => todo.id !== todoId),
    })),
}));

export default useTodoStore;

TodoList.tsx
useTodoStore 훅을 사용하여 상태와 액션을 가져올 수 있다. todos 배열 리스트를 보여주고 있으며, Delete 버튼을 클릭하면 removeTodo 함수를 호출하여 삭제할 수 있다. subscribe를 사용하여 todos의 상태가 변경될 때마다 로그를 출력할 수 있다.

import useTodoStore from './store';

export default function TodoList () {
  const { todos, removeTodo } = useTodoStore();

  useTodoStore.subscribe((state) => console.log(state.todos))
  
    <div>  return (

      <h2>Todo</h2>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            {todo.text}
            <button onClick={() => removeTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
};


TodoForm.tsx

useTodoStore 훅을 이용하여 addTodo 함수를 호출하여 form 제출시에 투두를 추가해준다.

import { useState } from 'react';
import useTodoStore from './store';

export default function TodoForm () {
  const [text, setText] = useState<string>('');
  const { addTodo } = useTodoStore();

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (!text.trim()) return;
    addTodo({ id: Date.now(), text });
    setText('');
  };
  
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    setText(e.target.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="투두를 입력해주세요."
        value={text}
        onChange={handleChange}
      />
      <button type="submit">Add</button>
    </form>
  );
};

Zustand 동작원리

CreateStoreImpl 함수는 let state로 상태를 저장하는 변수를 선언하고, const listeners는 리스너 함수를 저장하는 Set 객체를 생성한다. setState로 상태를 변경하는 메서드를 정의하고, partial은 부분적인 상태 변경, replace는 전체 상태를 교체할지 여부를 나타낸다.
nextState는 상태를 변경하기 전에 다음 상태를 계산하며, partial이 함수인 경우에 함수를 실행하여 변경될 상태를 얻고, 변경될 상태가 현재 상태와 다른 경우에만 상태를 업데이트한다.
여기서, replace가 지정되어 있거나 nextState가 객체가 아닌 경우에는 nextState를 그대로 할당하고, Object.assign을 사용하여 상태를 병합한다. listeners.forEach를 통해 모든 리스너의 새로운 상태와 이전 상태를 전달하여 호출한다. getState 함수는 현재 상태를 반환하고, getInitialState 함수는 초기 상태를 반환하며, subscribe 리스너를 등록하고 해제한다.

const createStoreImpl: CreateStoreImpl = (createState) => {
  type TState = ReturnType<typeof createState>
  type Listener = (state: TState, prevState: TState) => void
  
  let state: TState
  const listeners: Set<Listener> = new Set()

  const setState: StoreApi<TState>['setState'] = (partial, replace) => {
    const nextState =
      typeof partial === 'function'
        ? (partial as (state: TState) => TState)(state)
        : partial
    if (!Object.is(nextState, state)) {
      const previousState = state
      state =
        replace ?? (typeof nextState !== 'object' || nextState === null)
          ? (nextState as TState)
          : Object.assign({}, state, nextState)
      listeners.forEach((listener) => listener(state, previousState))
    }
  }

  const getState: StoreApi<TState>['getState'] = () => state

  const getInitialState: StoreApi<TState>['getInitialState'] = () =>
    initialState

  const subscribe: StoreApi<TState>['subscribe'] = (listener) => {
    listeners.add(listener)
    // Unsubscribe
    return () => listeners.delete(listener)
  }

참고자료 https://ui.toast.com/posts/ko_20210812 https://www.nextree.io/zustand https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts
profile
Leejungmin

0개의 댓글

관련 채용 정보