참고코드: GitHub - seungahhong/states-todos
설치
npm install zustand
or
yarn add zustand
zustand 개념
- 상태관리 설명
요즘 상태관리에 대한 접근 방식을 3가지로 나눠서 볼 수 있을 것 같습니다.
- Flux (Redux, Zustand)
- 일반적으로 사용하는 Flux 방식으로써, 저장소(store)/액션함수(action)/리듀서 등을 통해서 상태를 업데이트 하는 방식
- Proxy (Mobx, Valtio)
- 컴포넌트에 사용되는 일부 상태를 자동으로 감지해서 업데이트 하는 방식
- Atomic (Jotai, Recoil)
- React에 사용되는 state와 비슷하게 리액트 트리 안에서 상태를 저장하고 관리하는 방식
- Flux (Redux, Zustand)
- zustand 개념
- store 구현 방식 및 변경 방식이 간단함(redux에 비해 보일러플레이트 코드가 적다)
- Provider로 래핑할 필요가 없다.
- Context API를 사용할 때와 달리 상태 변경 시 불필요한 리렌더링일 일어나지 않는다.(변경된 상태만 업데이트)
- 특정 라이브러리에 엮이지 않는다(React와 함께 쓸 수 있는 API는 기본적으로 제공 → hooks 제공)
- 한 개의 중앙에 집중된 형식의 스토어 구조를 활용하면서, 상태를 정의하고 사용하는 방법이 단순하다.
- redux보다는 flux 구조와 유사함.
- 다양한 middleware 라이브러리를 제공(redux, immer, selector(상태변경 시 자동 호출), redux-devtools)
zustand 특징
-
Fetching everything store안에 상태값들 모두 얻어올 순 있지만, 모든 상태가 변경될 떄마다 컴포넌트가 업데이트 됨에따라서 선택해서 상태값을 얻어오도록 해야함.
const { todo, fetchTodo, fetchTodos } = useStore();
-
Selecting multiple state slices 기본적으로 strict compare(old === new) 변경될 시 컴포넌트를 렌더링 합니다. redux의 useSelector, jotai의 selectAtom, recoil의 selector 기능과 동일
const todo = useStore(state => state.todo); const fetchTodo = useStore(state => state.fetchTodo); const fetchTodos = useStore(state => state.fetchTodos);
-
Memoizing selectors React 함수형 컴포넌트에서 useCallback과 같이 사용한다면 selector를 메모할 수 있습니다. 이렇게 하면 렌더링할 때마다 불필요한 계산이 방지됩니다.
const todo = useStore(useCallback((state) => state.todo, [todo])
-
Using subscribe with selector 상태관리를 구독해서 상태값이 변경될 경우 호출될 수 있도록 처리가 가능합니다. mobx의 autorun/computed의 동일한 기능과 동일
import { subscribeWithSelector } from "zustand/middleware"; export const useStore = create<TodoState>()( subscribeWithSelector( todo: { todoItems: [], loading: true, error: false, }, ) ); // todoItems가 변경될 경우 subscribe 2번째 인자의 callback 함수 호출 useEffect(() => { // const todoLength = useMemo(() => todoItems.length, [todoItems]); useStore.subscribe( (state) => state.todo.todoItems, (todoItems) => console.log(todoItems.length) ); return () => { useStore.destroy(); }; }, []);
-
Async actions
fetchTodos: async () => { set((state) => { state.todo.loading = true; }); try { const response = await fetchTodo(); set((state) => { state.todo.loading = false; state.todo.todoItems = [].concat(response.data); }); } catch (e) { set((state) => { state.todo.loading = false; state.todo.error = e; }); } },
-
Read from state in actions
const useStore = create((set, get) => ({ todo: { todoItems: [], loading: true, error: false, }, action: () => { const todo = get().todo // ... } })
-
Immer middleware vs npm Immer Immer 라이브러리를 사용가능지만, Zustand middleware Immer 사용가능함.
import { immer } from 'zustand/middleware/immer'; export const useStore = create<TodoState>()( immer(set => ({ todo: { todoItems: [], loading: true, error: false, }, fetchTodos: async () => { set(state => { state.todo.loading = true; }); try { const response = await fetchTodo(); set(state => { state.todo.loading = false; state.todo.todoItems = [].concat(response.data); }); } catch (e) { set(state => { state.todo.loading = false; state.todo.error = e; }); } }, fetchTodo: async (id: number) => { set(state => { return produce(state, draft => { draft.todo.loading = true; }); }); try { const response = await fetchTodo(id); set(state => { return produce(state, draft => { draft.todo.loading = false; draft.todo.todoItems = [].concat(response.data); }); }); } catch (e) { set(state => produce(state, draft => { draft.todo.loading = false; draft.todo.error = e; }), ); } }, })), );
-
Redux devtools
import { devtools } from 'zustand/middleware'; const useStore = create(devtools(store));
zustand 그외 특징
- Using zustand without React :
GitHub - pmndrs/zustand: 🐻 Bear necessities for state management in React
- Transient updates (for often occurring state-changes)
GitHub - pmndrs/zustand: 🐻 Bear necessities for state management in React
- Middleware
GitHub - pmndrs/zustand: 🐻 Bear necessities for state management in React
- Overwriting state
GitHub - pmndrs/zustand: 🐻 Bear necessities for state management in React
- React context:
GitHub - pmndrs/zustand: 🐻 Bear necessities for state management in React
개인의견
- 상태관리(redux, contextapi)에 단점을 해소하기 위한다면 충분히 검토해볼만한 라이브러리 같습니다.
- 보일러플레이트 코드 최소화(action, reducer, state, type 등등)
- provider 래핑할 필요없음
- 자식 리렌더링을 상태별로 업데이트 가능
- redux,mobx 상태관리 보다는 쉽게 학습이 가능하고, 빠르게 도입해볼 수 있을 것 같습니다.
- zustand 자체에서 지원하는 라이브러리가 많아서 쉽게 사용이 가능할 것 같습니다.(단, 라이브러리 풀지원이 되는지 여부 확인)
- 리액트의 hooks 기반의 구조로 되어 있어서 확장성, 재사용성에 좋은 것 같습니다.
참고페이지
https://github.com/pmndrs/zustand
React 상태 관리 라이브러리 Zustand의 코드를 파헤쳐보자