개인프로젝트를 진행하며 라이브러리를 선정하는데 꽤 많은시간이 소요됐습니다.
요즘 프론트엔드는 가면 갈수록 라이브러리가 다양화되고 다양한 스펙이 추가되는 추세라 흐름을 쫒아가기 좀 어려운 게 사실입니다.
각설하고 해당 패턴을 사용하는 방법을 이야기 해보겠습니다.
이 패턴은 제가 zustand 를 사용하면서 devtools를 이용하여 상태가 관리되는 상황을 보던 중, 하나 이상의 스토어가 존재할 경우 redux-devtools 에서 마지막 스토어에 대한 정보만 보여주고 있어 확인해 보던중 아래와 같은 저와 같은 문제에 봉착한 StackOverflow의 글을 찾게됐습니다.
이글에서 두개의 스토어를 만들었는데 두 스토어의 동작을 모두 보고싶다는 질문입니다.
답변은 아래와 같습니다.
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이 뭔지 읽어 보라는 댓글이었고 이 글을 보고 저도 사용해 보기로 했습니다.
Slicing the store into smaller stores
위 제목으로 시작합니다. "스토어를 더 작은 스토어로 분할 한다" 라는 것이고
더 많은 기능을 추가할수록 스토어는 점점 더 커지고 관리에 어려움이 있을 수 있으니, 상위 스토어를 만들고 거기에 개별적인 스토어를 결합하라는 것인데요
이를 통해서 상위스토어에 하위스토어들을 병합하여 사용하라는 것으로 이해할 수 있습니다.
저는 타입스크립트를 사용하므로 문서의 아래로 내리면 타입스크립트와 함께 사용하는 법이 있습니다.
저의 경우 meeting, session 라는 두개의 스토어를 이용해서 스토어를 관리하고 있었고, 이 두개를 하위스토어로 사용하기 위해 기존의 코드에서 변경을 진행했습니다.
MeetingStore 의 기능은 F1 이 개최되는 국가의 정보를 담고 있는 스토어 입니다.
아래의 기능은 UI 의 리스트를 선택했을 경우 국가의 정보를 set
하고 국가의 값을 상태로 가지고 있는 스토어 입니다.
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' },
),
)
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),
})),
)
devtools
추가하위 스토어가 받는 StateCreator
의 인자는 set
, get
, api
가 존재합니다. 그렇기 때문에 ...args
로 넘기게 됩니다.
스토어를 사용할 파일로 이동하여 const { setSelectedMeeting } = useSliceMergeStore()
로 스토어에 정의 된 함수를 사용합니다.
위 처럼 병합한 스토어에서 정의한 함수, 상태를 불러올 수 있습니다.
저는 이 파일에서 setSelectedMeeting
함수를 사용했습니다.
기존에 나눠진 스토어 방식에서는 하나만 보이던 스토어의 정보가 두개 모두 잘 보이는 모습을 볼 수 있습니다.
UI 에서 F1이 개최되는 국가를 선택하게 되면 아래와 같이 변경되는 값을 볼 수 있습니다.
헝가리가 저장되는 것을 볼 수 있습니다.
사실 다양한 스펙중 zustand 를 사용한 이유는 redux 는 이제 너무 많은 양의 보일러플레이트가 존재하기 때문에 flux 패턴을 사용하던 저와 같은 개발자는 zustand 로 넘어가는 추세인것 같습니다. 이미 프로덕션 환경에서 적용하여 사용하는 곳도 있는 것 같고 redux 환경 구축에 지친 개발자 분들이라면 한번쯤 토이프로젝트를 통해 접해보는 것을 추천드립니다.
이 글은 제가 처음 velog 에 기재하는 글입니다.
글이 많이 부족하다고 느끼셨다면 발전해나가도록 하겠습니다.