공식 문서
설치하기
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 함수를 통해 상태를 비동기적으로 업데이트합니다.
스토어 사용하기
훅 사용
BearCounter와 Controls,는 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;