:ledger: Next.js Zustand 개념 및 사용법을 알아보자!

Zustand는 정말 가벼운 상태 관리 라이브러리로, 정말 많은 기업에서 사용하고 있다. 사용법은 무엇인지 개념은 어떻게 되는지 알아보도록 하자!

:one: Zustand 주요 장점

Zustand는 Hooks를 사용하여 편리한 API를 제공하며, 작고 빠르며 확장성이 좋다.

  1. 보일러플레이트의 최소화
    • 반복적으로 사용해야하는 코드 양을 최소화하여 프로젝트의 유지보수를 쉽게 한다.
  2. 아주 작은 크기
    • Zustand의 핵심 로직은 바닐라 자바스크립트 수십줄로 구성되어 있는 만큼, 매우 가벼운 라이브러리이다.
  3. 효율적인 렌더링
    • 상태가 변경될 때만 컴포넌트를 렌더링하므로, 불필요한 리렌더링을 방지하여 성능을 향상 시킨다.

:two: Zustand 사용법 (counter)

Next.js에서 간단한 카운터 앱을 만들어 보며 Zustand를 어떻게 사용하는지 알아보자

:pushpin: 2-1) 설치 방법

아래의 명령어를 터미널에 입력한다.

## npm
npm install zustand 

## yarn
yarn add zustand

:pushpin: 2-2) counter store 생성

app/store 디렉토리에 스토어를 생성해준다.

// src/store/useCounterStore.ts
import { create } from 'zustand';

interface CounterStore {
  count: number;
  increment: () => void;
  decrement: () => void;
}

const useCounterStore = create<CounterStore>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

export default useCounterStore;

:pushpin: 2-3) Store 사용하기

useCounterStore에서 정의한 상태값과 함수를 import해서 사용한다.

"use client";

import counterStore from '../store/counterStore';

const CounterPage = () => {
  const { count, increment, decrement } = useCounterStore();

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

export default CounterPage;

:three: Zustand 사용법 (todo list)

Next.js에서 투두 리스트를 만들어보며 Zustand를 어떻게 사용하는지 알아보자

:pushpin: 3-2) todo store 생성

app/store 디렉토리에 스토어를 생성해준다.

// src/store/todoStore.ts
import { create } from 'zustand';

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

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

const useTodoStore = create<TodoStore>((set) => ({
  todos: [],
  addTodo: (todo) => set((state) => ({
    todos: [...state.todos, todo],
  })),
  removeTodo: (id) => set((state) => ({
    todos: state.todos.filter((todo) => todo.id !== id),
  })),
  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map((todo) =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ),
  })),
  completeTodo: (id) => set((state) => ({
    todos: state.todos.map((todo) =>
      todo.id === id ? { ...todo, completed: true } : todo
    ),
  })),  
}));

export default useTodoStore;

:pushpin: 3-3) TodoFrom.tsx

  • addTodo: addTodo(todo.id)를 호출하여 Todo를 완료로 표시한다.
// src/_components/TodoForm.tsx
"use client";

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

// FC: 함수형 컴포넌트 정의
const TodoForm: FC = () => {
  const [text, setText] = useState('');
  // Zustand 스토어에서 addTodo 가져옴
  const addTodo = useTodoStore((state) => state.addTodo);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (text.trim()) {
      const newTodo = { id: Date.now(), text, completed: false };
      addTodo(newTodo);
      setText('');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="할 일을 입력하세요"
      />
      <button type="submit">추가</button>
    </form>
  );
};

export default TodoForm;

:pushpin: 3-3) TodoList.tsx

  • toggleTodo: toggleTodo(todo.id)를 호출하여 Todo의 완료 상태를 토글한다.
  • completeTodo: completeTodo(todo.id)를 호출하여 Todo를 완료로 표시한다.
  • removeTodo: removeTodo(todo.id)를 호출하여 Todo를 삭제한다.
// src/_components/TodoList.tsx

"use client";

import { FC } from 'react';
import useTodoStore from '../store/todoStore';

const TodoList: FC = () => {
  const { todos, removeTodo, toggleTodo, completeTodo } = useTodoStore();

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          <span style=>
            {todo.text}
          </span>
          <button onClick={() => toggleTodo(todo.id)}>Toggle</button>
          <button onClick={() => completeTodo(todo.id)}>Complete</button>
          <button onClick={() => removeTodo(todo.id)}>Remove</button>
        </li>
      ))}
    </ul>
  );
};

export default TodoList;

:fire: 마무리

이전에 React로 제작한 팀 프로젝트 - 가을축제핑에서 사용해보았는데, 기억이 잘 안났던 부분도 있고 Next.js에서는 어떻게 사용해보는지 알아보기위해 Counter, Todo 기능을 구현해보면서 다시 한번 문법이 간결하고 정말 편리하다고 느꼈다.