Slice 패턴으로 구성된 Zustand Store의 일부 상태를 TypeScript 문법을 사용하여 Persist하는 법

Hyungeol94·2023년 12월 6일
2
post-thumbnail

문제의 배경

오픈마켓 프로젝트를 진행하면서 상태관리 라이브러리로 Zustand를 사용하고 있다. 우리 팀은 Zustand의 공식문서를 참고하여, 도메인별로(Auth, Order, MyPage, Product 등등) 슬라이스를 만들어 놓고, 그것을 index.ts에서 합쳐서 하나의 Storecreate하여 사용하려고 했다.

///초기의 index.ts 코드
import { createAuthSlice } from "./authSlice";
import { productSlice } from "./productSlice";

export const useBoundStore = create<AuthSlice & ProductSlice>()((...a) => ({
  ...createAuthSlice(...a),
  ...createProductSlice(...a),
}));

로그인을 한 다음 api에서 받아오는 토큰들을 AuthSlice안에서 관리하게 되면서 문제가 조금 복잡해졌다.

  1. Token을 전역상태에 저장하여 새로고침 등 re-rendering이 일어날 때에 사라지지 않게 해야 한다. -> persist를 사용
  2. TypeScript 기반이라면 persistcreate와 함께 사용함이 권장된다(아래 공식문서 링크 참고).
  3. Slice에서 실제로 create를 사용하지는 않는다.

선택지와 결정

(3)에 의해, 직접적으로 slice를 구현하는 부분에서 persist를 적용하는 것은 쓰는 것은 어렵게 되었다. persist를 사용하는 방법은 두 갈래가 있었는데, 모든 slice들이 합쳐진 boundStore에서 persist를 사용하거나, 아니면 AuthSlice에 어떻게든 create를 사용해서 persist를 사용해 보는 것이다.

후자에 관해서는 이를 가능하게 해 줄 오픈소스 라이브러리가 존재하는 것을 확인하였다. zfy라는 라이브러리에서서 제공하는 InitStores라는 메서드를 통해서 여러 갈래로 create된 store들을 묶을 수 있는 것처럼 보였다. (링크는 아래 참고)

그렇지만 오픈소스 라이브러리의 한계상 버전에 민감한 특성이나 예기치 못한 오류에 대처하기가 어려울 것 같아 전자의 방법을 최대한 해 보기로 하였다.

storepersist를 할 때는 다양한 옵션들을 추가할 수 있었는데, partialize라는 옵션을 지정해서 스토리지(로컬, 세선 등등)에 저장할 상태의 일부분을 지정할 수 있었다. 그렇다면 boundStore 전체가 스토리지에 들어가는 우려스러운 상황을 방지할 수 있고, 따라서 애초에 일부 slice만을 persist하여 해결하고자 했던 문제상황을 해결할 수 있게 되는 것이었다.

결과

//persist를 사용하여 수정된 코드
export const useBoundStore = create<
  AuthSlice & MyPageSlice
  >()(
    devtools(
      persist(
      (...a) => (
        {
          ...createAuthSlice(...a),
          ...createMyPageSlice(...a),
      })
    ,{name: 'boundStore',
      partialize: (state) => ({
        userToken : state.userToken,
        userBasicInfo : state.userBasicInfo,
        isLoggedIn : state.isLoggedIn,
     })
    }
  )))

공식문서의 persist에 관한 설명 부분에는 devtools를 쓰는 것이 없었는데, TypeScript Guide 부분에는 persistdevtools로 감싸진 것을 확인하였기 때문에 그대로 넣어주었다. 이렇게 하니 로컬스토리지에 partialize한 상태들만 저장이 잘 되는 것을 확인할 수 있었다.

persist에 넣어주는 option object에는 partialize 말고도 저장될 스토리지 타입을 지정해줄 수도 있었다. 만약 스토리지 타입을 지정해주지 않는다면 기본으로 로컬스토리지가 지정이 된다. 나중에 한 줄 추가만 해주면 되니 편리하다.

이로써 어디에서는 useBoundStore를 통해서 token을 가져올 수 있게 함에 따라, 매끄러운 서비스 연결을 위한 중요한 발판 하나를 마련할 수 있었다.

번외) Zustand의 슬라이스 패턴에서는 슬라이스를 모아 왜 한 군데에서 create를 할까?

조원 중에 한 분이 이런 질문을 했는데 아무런 답도 찾지 못했고, 대신 챗GPT가 대답을 해주었다. 성능 및 메모리 관리, 일관성과 유지보수 측면에서 강점이 있기 때문에 그렇다고 한다.

Q: Zustand는 해당 라이브러리내에서 슬라이스라는 개념이 있고 리덕스와 비슷하게 활용하는것 같은데, 그럼 zustand에서 스토어를 여러개 사용할수있다면 왜 굳이 슬라이스 개념을 사용해서 하나의 스토어로 combine해서 해야하는지 모르겠어.
그 이유를 자세히 알려줘.

A. Zustand와 리덕스는 상태 관리를 위한 라이브러리이지만, 몇 가지 차이점이 있습니다. Zustand가 여러 개의 스토어를 사용할 수 있는 것은 맞지만, 왜 슬라이스와 같은 개념을 사용하여 하나의 스토어로 combine하는지에 대한 이유는 여러 가지가 있습니다.

성능 및 메모리 관리: 여러 개의 작은 스토어를 사용하는 것보다 하나의 큰 스토어를 사용하는 경우 메모리 사용량 및 전체적인 성능에 이점이 있을 수 있습니다. 또한, 하나의 스토어에서 상태를 변경할 때마다 리렌더링이 발생하므로 상태 간의 의존성이 있는 경우에는 하나의 스토어에서 더 쉽게 처리할 수 있습니다.

일관성과 유지보수: 하나의 스토어를 사용하면 애플리케이션의 전반적인 상태를 한곳에서 쉽게 관리할 수 있습니다. 이는 코드의 일관성을 유지하고 유지보수를 쉽게 만들어줍니다. 스토어가 분리되어 있다면, 각각의 스토어에서 상태를 변경하고 관리해야 하기 때문에 코드가 복잡해질 수 있습니다.

중앙 집중화된 로직 및 미들웨어 적용: 리덕스는 미들웨어를 통한 중앙 집중화된 로직 적용이 쉽습니다. 하나의 스토어에서 액션을 디스패치하고 미들웨어에서 처리할 수 있습니다. Zustand도 비슷한 패턴을 사용할 수 있지만, 하나의 스토어로 중앙 집중화된 로직을 적용하는 것이 효과적일 수 있습니다.

그러나 상황에 따라 여러 개의 스토어를 사용하는 것이 나을 수도 있습니다. 예를 들어, 서로 독립적인 모듈 또는 컴포넌트가 각각의 스토어를 가지고 있고, 이들 간에 강한 의존성이 없다면 여러 개의 스토어를 사용하여 컴포넌트를 분리하고 유지보수성을 높일 수 있습니다. 따라서 사용하고자 하는 상황 및 애플리케이션의 구조에 따라서 선택하는 것이 중요합니다.

참고 링크 ------------------------------------------------------------------------------------------------
1)공식 문서에서 create와 persist를 함께 사용하라고 나온 부분 -> "Just make sure you are using them(persist method) immediately inside create so as to make the contextual inference work. Doing something even remotely fancy like the following myMiddlewares would require more advanced types."
출처:https://docs.pmnd.rs/zustand/guides/typescript#using-middlewares.
2) zfy 라이브러리 공식문서
https://colorfy-software.gitbook.io/zfy/guides/handling-multiple-stores

profile
가치를 전달하는 개발을 지향합니다.

0개의 댓글