Zustand를 사용해보자!

박영은·2024년 6월 10일
0

zustand는 기본적으로 새로 고침 시 데이터들이 날아감.
zustand persist
전역 상태를 사용하는데, 새로 고침 후에도 값을 사용하도록 스토리지에 값을 저장해야하는 경우 persist를 이용하면 된다.
"Persist 미들웨어를 사용하면 Zustand 상태를 저장소(예: localStorage, AsyncStorage, IndexedDB등)에 저장하여 해당 데이터를 유지할 수 있다."

zustand를 immer와 persist라는 내장 미들웨어가 있습니다.
create 함수 내에 persist(()=> (), { }) 형태로 작성
immer를 이용해 복잡한 객체의 업데이트를 간단히 처리할 수 있습니다.
create 함수 안에 immer로 감싸기만 하면 됩니다.

import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer' // npm install immer 필요

const useBeeStore = create(
  immer((set) => ({ // <- 바로 요기 부분
    bees: 0,
    addBees: (by) =>
      set((state) => {
        state.bees += by
      }),
  }))
)

다음과 같이 persist를 이용해 storage에 저장할 수 있고 로컬 스토리지뿐만 아니라 세션스토리지도 지원합니다.

(Recoil에서는 새로고침시에 데이터 유지를 위해 recoil-persist라는 파생라이브러리르 사용하거나 별도의 로직을 만들어 저장하는 과정을 수행해야 했습니다.)

import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

const useFishStore = create(
  persist(
    (set, get) => ({
      fishes: 0,
      addAFish: () => set({ fishes: get().fishes + 1 }),
    }),
    {
      name: 'food-storage', // unique name
      storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
    }
  )
)

고수 팁 ) 비구조화 할당 문법으로 원하는 것만 가져다 쓰기 가능.

// e.g.

import { shallow } from 'zustand/shallow'

// 일반 사용법
const { increment } = useCounter();

// 고수 사용법
const increment = useCounter(state => state.increment);

// 고수 사용법 2 shallow
const { increment, decrement } = useCounter((state) => ({
  increment: state.increment,
  decrement: state.decrement,
}),shallow);

이 두 개의 차이점은 비교로직 차이입니다.

react의 비교로직과 달리 state로 가져온 고수 사용법은 === 연산자를 쓰기 때문에 조금 더 효율적인 랜더링이 가능합니다. ( zustand가 내세우는 장점 중에 하나입니다 )


타입스크립트 이용해 간단하게 초기화 하기.

// CounterStore.tsx
import create from "zustand";

interface CounterState {
  count: number;
}

const intialState = {
  count: 0,
};

export interface CounterStore extends CounterState {
  increment: () => void;
  decrement: () => void;
  resetCounterStore: () => void;
}

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

export default useCounterStore;

initalState를 파일 내부에 선언하고 reset에서 값을 업데이트해 주는 형식입니다.

그래서 초기화함수를 활용할 수도 있고 코드를 분리함으로써 초기데이터를 읽기도 쉬워졌습니다.


타입스크립트와 함께 쓰기.

// state와ㅗ action을 동등한 계층으로 사용해본 예시.
// CounterStore.tsx
// state와 actions로 분리한 type 사용예시
type State = {
  count: number;
};

type Actions = {
  increment: () => void;
  decrement: () => void;
  resetCounterStore: () => void;
};

const initialState: State = {
  count: 0,
};

const useCounterStore = create<State & Actions>((set) => ({
  ...initialState,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  resetCounterStore: () => set(initialState),
}));

export default useCounterStore;

파생 데이터 다루기

const useStore = create((set) => ({
  first: 'Daishi',
  last: 'Kato',
  get fullName() {
    return `${this.first} ${this.last}`;
  },
}));

//이렇게 하면 computed 값 같은 경우는 골치가 아픕니다.
// 공식 지원은 아니고 별도의 라이브러리 (zustand-middleware-computed-state)가 있지만 기본 라이브러리만 사용해서 작업한다며 ㄴ아래와 같이 차라리 별도로 빼서 하는 방법도 있다.

import create from 'zustand';
import { useMemo } from "react";

// 선언
const useCounter = create((set) => ({
  count: 0,
  increment: () => set(state => ({ count: state.count + 1 })),
  decrement: () => set(state => ({ count: state.count - 1 }))
}));

const useCounterDerived = () => {
  const state = useCounter();

  const compuntedCount = useMemo(() => {
    // ... 생략
  }, [state.count]);

  return {
    compuntedCount
  }
};


출처1

출처2

profile
Front-end

0개의 댓글