현재 리액트 상태관리 라이브러리는 다양하게 존재한다. 대표적으로 Context API, Redux, MobX, Recoil, Jotai 등이 있다.
이 중 Redux가 상태관리 라이브러리의 시초격이라 할 수 있다. 그렇기 때문에 아직까지 많은 개발자들로 부터 사용되고 있으며 여전히 인기가 많다. 하지만 시간이 지날수록 상태관리 라이브러리로 Redux를 기피하고 걷어내고 있는 이유도 있다. 그것이 바로 보일러 플레이트 코드 때문이다. 즉, 반복적으로 비슷한 코드들이 여러곳에서 재사용되며, 비슷한 형태를 띄우는 코드를 뜻한다. 물론 Redux Toolkit이 이러한 점을 극복하고 있고 계속해서 업데이트되고는 있지만 여전히 보일러플레이트가 존재하며 문법이나 사용하는 방법이 익히는 데 어렵고 난해한 편이라고 생각한다.
그럼 최소한의 코드로 상태를 관리하는 방법은 없을까? 본인은 Recoil과 Context API를 사용해 보았고 이번에 기업 과제로서 처음으로 Zustand를 사용하게 되었다. 방식은 기존 Recoil과 유사했지만 큰 차이점은 rootComponent를 특정 컴포넌트로 감쌀 필요가 없었다. 예를 들어, Recoil은 <RecoilRoot></RecoilRoot> 혹은 Context로 Provider를 감싸주지 않아도 되었다.
Zustand?
Zustand는 독일어로 상태라는 뜻을 가졌다. 또 하나의 상태관리 라이브러리인 Jotai를 만든 카토 다이시가 제작에 참여하며 만든 것으로 알려져 있다.
제일 큰 장점은 일단 너무 쉽다. Recoil도 root에 컴포넌트만 감싸기만 해도 state를 관리할 수 있었다. 하지만 Zustand는 여기서 감싸지도 않고 쉽게 사용할 수 있다. 즉, 부모 자식간의 컴포넌트 관계가 사라지고, 불필요한 렌더링을 막아주는 장점이 있다. 또한 간단하게 Store 파일을 만들어 여기서만 정해진 함수로 상태를 바꿀 수 있게 관리를 해주면 전역적으로 상태관리를 사용할 수 있다.
특징
- 특정 라이브러리에 엮이지 않는다. (그래도 React와 함께 쓸 수 있는 API는 기본적으로 제공한다.)
- 한 개의 중앙에 집중된 형식의 스토어 구조를 활용하면서, 상태를 정의하고 사용하는 방법이 단순하다.
- Context API를 사용할 때와 달리 상태 변경 시 불필요한 리랜더링을 일으키지 않도록 제어하기 쉽다.
- React에 직접적으로 의존하지 않기 때문에 자주 바뀌는 상태를 직접 제어할 수 있는 방법도 제공한다. (Transient Update라고 한다.)
- 동작을 이해하기 위해 알아야 하는 코드 양이 아주 적다. 핵심 로직의 코드 줄 수가 약 42줄밖에 되지 않는다. (VanillaJS 기준)
출처 : https://ui.toast.com/posts/ko_20210812
Zustand 사용법
npm i zustand
yarn add zustand
- 먼저 zustand library를 설치한다.
적용 store 생성
// store.ts
import create from "zustand";
export interface ProductStoreProps {
productsData: ProductItems[];
setProductsData: Dispatch<SetStateAction<ProductItems[] | any>>;
sortByLowScore: () => void;
sortByHighScore: () => void;
}
// create를 이용해서 store을 생상헐 수 있으며, 다수의 store도 생성 가능하다.
export const useProductStore = create<ProductStoreProps>(set => ({
productsData: [],
// data라는 데이터를 인자로 받아서 productsData안에 저장시켜주는 역할의 함수이다.
setProductsData: (data: ProductItems[]) => set({ productsData: data }),
// set((state) => {}) 함수로 현재 state안에 있는 productsData를 가져와서 sort 해 주는 함수이다.
sortByLowScore: () =>
set(state => ({
productsData: state.productsData.sort((a, b) => a.price - b.price),
})),
sortByHighScore: () =>
set(state => ({
productsData: state.productsData.sort((a, b) => b.price - a.price),
})),
}));
- 스토어를 만들 때는 create 함수를 이용하여 상태와 그 상태를 변경하는 액션을 정의한다.
- sotre는 Hook기반으로 하는 편리한 API이기 때문에 어떤 것이든 넣을 수 있다. (원시 타입, 객체 , 함수)
- set 함수는 상태를 병합 즉, Merge 한다.
import { useProductStore } from "@store/store";
const { sortByLowScore, sortByHighScore } = useProductStore();
- store에 선언한 값 혹은 메서드들을 간단하게 하나씩 꺼내와서 쓰면 끝이다.
- 컴포넌트에서 store 훅을 사용할 때는 스토어에서 상태를 어떤 형태로 꺼내올지 결정하는 셀렉터 함수를 전달해 주어야 한다. 만약 셀렉터 함수를 전달하지 않는다면 스토어 전체가 리턴된다.
- 구조분해 할당으로 원하는 함수를 꺼내올 수 있다.
Persist Middleware
Persist Middleware는 저장된 zustand state를 스토리지에 저장할 수 있게 만들어 준다 (로컬 스토리지, 세션 스토리지, 등등)
import create from 'zustand'
import { persist } from 'zustand/middleware'
export const useFishStore = create(
persist(
(set, get) => ({
fishes: 0,
addAFish: () => set({ fishes: get().fishes + 1 }),
}),
{
name: 'food-storage', // 반드시 유니크한 이름을 지어줘야한다.
getStorage: () => sessionStorage,
}
)
)
- 기능적으로 원하는 방식으로 store를 구성할 수 있다.
- 기본 설정 값은 () ⇒ localStorage이다.
// store.ts
export const useCartStore = create<CartStoreProps>()(
persist(
(set, get) => ({
cartData: [],
setCartData: (id: number) =>
set({
cartData: [...get().cartData, { id, quantity: 1, checked: true }],
}),
removeItem: (id: number) =>
set({
cartData: get().cartData.filter(
(data: CartItemProps) => data.id !== id
),
}),
setCartChecked: (id: number) =>
set({
cartData: [
...get().cartData.map((data: CartItemProps) => {
if (data.id === id) {
return {
id,
quantity: data.quantity,
checked: !data.checked,
};
} else {
return data;
}
}),
],
}),
// 생략...
}),
{
name: "cart-items",
getStorage: () => localStorage,
}
)
);
- 이렇게 설정했더니 따로 localStorage에 저장할 필요가 없다. 변경이 이루어질 때 마다 알아서 localStorage에 변경된 내역이 저장된다.
마무리
확실히 아직까지는 Redux가 가장 많이 쓰이고 있으며 시장을 압도하고 있기는 하다. 하지만 Redux를 제외하고는 압도적으로 성장하고 있는 점을 보아 곧 mobX를 넘어서 Redux를 위협할 수 있을 것 같다. 엄청 편리하고 사용법이 간단하지만 다양한 옵션들이 있기 때문에 좀 더 공부해 보자.
출처 : 아래의 사이트들을 보면서 큰 공부 하였습니다
https://ui.toast.com/posts/ko_20210812
https://docs.pmnd.rs/zustand/integrations/persisting-store-data
https://blacklobster.tistory.com/3
https://velog.io/@hinyc/Zustand-초간단-전역-상태관리-ft.Typescript