Zustand 의 Slices pattern 사용법

김주현·2024년 7월 19일
0

F1 Info

목록 보기
2/3

개인프로젝트를 진행하며 라이브러리를 선정하는데 꽤 많은시간이 소요됐습니다.
요즘 프론트엔드는 가면 갈수록 라이브러리가 다양화되고 다양한 스펙이 추가되는 추세라 흐름을 쫒아가기 좀 어려운 게 사실입니다.

각설하고 해당 패턴을 사용하는 방법을 이야기 해보겠습니다.


왜 Slices pattern 사용했을까?

이 패턴은 제가 zustand 를 사용하면서 devtools를 이용하여 상태가 관리되는 상황을 보던 중, 하나 이상의 스토어가 존재할 경우 redux-devtools 에서 마지막 스토어에 대한 정보만 보여주고 있어 확인해 보던중 아래와 같은 저와 같은 문제에 봉착한 StackOverflow의 글을 찾게됐습니다.

https://stackoverflow.com/questions/77406827/how-to-handle-more-than-one-zustand-store-into-the-browser-devtools

이글에서 두개의 스토어를 만들었는데 두 스토어의 동작을 모두 보고싶다는 질문입니다.

답변은 아래와 같습니다.

const Store1 = create(devtools((set) => ..., { name, store: storeName1 }))
const store2 = create(devtools((set) => ..., { name, store: storeName2 }))

OR

const useStore = create(
  devtools((...a) => ({
    ...createStoreSlice1(...a),
    ...createStoreSlice2(...a)
  }))
);

거기에 좋은 댓글까지 달려있었습니다.
https://docs.pmnd.rs/zustand/guides/slices-pattern 에서 Slices pattern이 뭔지 읽어 보라는 댓글이었고 이 글을 보고 저도 사용해 보기로 했습니다.

Zustand Slices pattern 적용

Slicing the store into smaller stores

위 제목으로 시작합니다. "스토어를 더 작은 스토어로 분할 한다" 라는 것이고

더 많은 기능을 추가할수록 스토어는 점점 더 커지고 관리에 어려움이 있을 수 있으니, 상위 스토어를 만들고 거기에 개별적인 스토어를 결합하라는 것인데요
이를 통해서 상위스토어에 하위스토어들을 병합하여 사용하라는 것으로 이해할 수 있습니다.

저는 타입스크립트를 사용하므로 문서의 아래로 내리면 타입스크립트와 함께 사용하는 법이 있습니다.

저의 경우 meeting, session 라는 두개의 스토어를 이용해서 스토어를 관리하고 있었고, 이 두개를 하위스토어로 사용하기 위해 기존의 코드에서 변경을 진행했습니다.

기존 스토어 수정

MeetingStore 의 기능은 F1 이 개최되는 국가의 정보를 담고 있는 스토어 입니다.
아래의 기능은 UI 의 리스트를 선택했을 경우 국가의 정보를 set 하고 국가의 값을 상태로 가지고 있는 스토어 입니다.

변경 전 - MeetingStore

import { StateCreator } from 'zustand'
import { MeetingInterface } from '../features/meetings/useMeetings'

interface MeetingsState {
    meetingState: {
        countryName: MeetingInterface['country_name'] | null	// 국가정보
    }
    setSelectedMeeting: (meeting: MeetingInterface | null) => void	// 국가를 저장하는 함수
}


export const useMeetingStore = create<MeetingsState>()(
    devtools(
        (set) => ({
            meetingState: {
                countryName: null,
            },
            setSelectedMeeting: (meeting: MeetingInterface | null) =>
                set({
                    meetingState: { countryName: meeting?.country_name || null },
                }),
        }),
        { name: 'MeetingStore' },
    ),
)

변경 후 - MeetingStore

import { StateCreator } from 'zustand'
import { MeetingInterface } from '../features/meetings/useMeetings'

export interface MeetingSliceInterface {
    meetingState: {
        countryName: MeetingInterface['country_name'] | null	// 국가정보 
    }
    setSelectedMeeting: (meeting: MeetingInterface | null) => void	// 국가를 저장하는 함수
}


export const meetingSlice: StateCreator<MeetingSliceInterface> = (set) => ({
    meetingState: {
        countryName: null,
    },
    setSelectedMeeting: (meeting: MeetingInterface | null) =>
        set({
            meetingState: { countryName: meeting?.country_name || null },
        }),
})

변경점

  • export Interface 로 변경 export 로 내보내는 이유는 상위 스토어를 생성할때 타입이 필요하기 때문입니다.
  • create 로 생성하던 스토어를 StateCreator 로 변경
  • devtools 제거
  • 변수명, 타입명 변경 (이건 개개인의 개발성향에 따라 수정하시면 됩니다.)

meeting 과 같이 session 스토어도 변경을 하였습니다. 위 변경점과 동일하게 수정하였습니다.


상위 스토어 생성 후 사용

상위 스토어 생성

useSliceMergeStore.ts 라는 파일을 만들었고 코드는 아래와 같이 작성했습니다.

import { devtools } from 'zustand/middleware'
import { create } from 'zustand'
import { sessionSlice, sessionSliceInterface } from './sessionSlice'
import { meetingSlice, MeetingSliceInterface } from './meetingSlice'

export const useSliceMergeStore = create<sessionSliceInterface & MeetingSliceInterface>()(
    devtools((...args) => ({
        ...sessionSlice(...args),
        ...meetingSlice(...args),
    })),
)
  • 하위 스토어인 meeting, session 를 하나의 스토어로 결합
  • 하위 스토어에서 정의한 타입을 재사용
  • devtools 추가

하위 스토어가 받는 StateCreator 의 인자는 set, get, api 가 존재합니다. 그렇기 때문에 ...args 로 넘기게 됩니다.

사용

스토어를 사용할 파일로 이동하여 const { setSelectedMeeting } = useSliceMergeStore() 로 스토어에 정의 된 함수를 사용합니다.

위 처럼 병합한 스토어에서 정의한 함수, 상태를 불러올 수 있습니다.
저는 이 파일에서 setSelectedMeeting 함수를 사용했습니다.


결과

기존에 나눠진 스토어 방식에서는 하나만 보이던 스토어의 정보가 두개 모두 잘 보이는 모습을 볼 수 있습니다.

UI 에서 F1이 개최되는 국가를 선택하게 되면 아래와 같이 변경되는 값을 볼 수 있습니다.

헝가리가 저장되는 것을 볼 수 있습니다.


글을 마치며

사실 다양한 스펙중 zustand 를 사용한 이유는 redux 는 이제 너무 많은 양의 보일러플레이트가 존재하기 때문에 flux 패턴을 사용하던 저와 같은 개발자는 zustand 로 넘어가는 추세인것 같습니다. 이미 프로덕션 환경에서 적용하여 사용하는 곳도 있는 것 같고 redux 환경 구축에 지친 개발자 분들이라면 한번쯤 토이프로젝트를 통해 접해보는 것을 추천드립니다.

이 글은 제가 처음 velog 에 기재하는 글입니다.
글이 많이 부족하다고 느끼셨다면 발전해나가도록 하겠습니다.

profile
돈은 목적이 아닌 수단이다.

0개의 댓글