[ 공모전 ] Auth : 토큰과 전역관리(zustand.persist,create)

최문길·2024년 7월 27일
0

공모전

목록 보기
32/46
post-thumbnail

이전 포스팅에서

  1. 전역적으로 사용할 것이므로, 전역관리 상태를 사용하여햐 했고,
  2. 혹시나 하는 새로고침에도, 값이 사라지지 않아야 하므로,
    localStorage, SessionStorage중 하나에 저장해서 토큰 값을 받아오면 우선적으로 브라우저의 스토리지에 저장하고 그 값을 전역에 저장하여서 관리하기로 생각하였다.

위의 두가지를 사용해서 토큰을 전역하기에 zustand의 미들웨어인 persist 를 사용하여 토큰관리를 하기로 하였다.
우선 persist의 기초 로직과 그리고 내가 어떻게 작성했는지를 알아보자.

Zustand basic

지금 작성하는 글은 타입스크립트와 zustand를 같이 사용하기에, 훅이 두 번 호출 되는 것에 의아할 수 있는데,
작성해두었던 포스팅이 있기에 확인하고 오길 바라~~

그래도 난 친절하니깐( 지금은 블로그 작성기간이라 또 까먹으면 안되니까 ...)

interface CountType {
  count: number;
  //getCount: CountType["count"];
  getCount: ()=>CountType["count"];
  setCount: (number: number) => void;
}
// create<T>()(...) https://docs.pmnd.rs/zustand/guides/typescript 여기서 why the currying을 참조하자
export const useCount = create<CountType>()((set, get) => ({
  count: 0,
  getCount: ()=>get().count, // get().count라고 사용하면 안된다.. 함수로 감싸서 반환해주자
  setCount: (number) =>
    set((state) => ({ // state => CountType이 담겨있다. 
// ...state, 이걸 안해줘두 된다. https://docs.pmnd.rs/zustand/guides/immutable-state-and-merging 여기서 참고하자.
      count: state.count++,
    })),
}));

// action & value 

// value
export const useCount = create<CountType>()(()=>({
  count: 0
}))

// action
export const setCount =(number)=> useCount.setState((state)=>({count:number}))
export const increaseCount = ()=>useCount.setState((state)=>({count:count++}))// 안감싸주고 호출하면 무한 render
// value얻기
const {count} = useCount(state=>state.count);
// initalValue값 얻기 
const initalValue = useCount.getInitialValue(); || useCount.getInitialValue['property']

create 함수를 호출하면 콜백함수가 들어가게 되는데, 콜백함수 안에는 setget이라는 콜백함수 파라미터가 있다.

  • set을 호출하면 set의 콜백함수안에는 useCount에 저장되어있는 valueaction 정보가 담겨있다.
  • get 을 호출하면 useCount에 저장되어있는 valueaction이 담겨있다.

난 보통

interface I_Count { count:number };
const initialValue = {count:0};
const useCount = create<I_Count>()(()=>(...initialValue));
const resetCount = ()=>useCount.setCount(state=>({...initialValue}) 
const setCount =(number)=> useCount.setState((state)=>({count:number}))
const increaseCount = ()=>useCount.setState((state)=>({count:count++}))// 무한 렌더링...조심하자 wrapping하자

Zustand의 Persist

위에서 작성한 것을 토대로 속성으로 알아보았는데
zustand는 redux와 유사한 flux pattern이며, 용량 자체도 redux대비 10배이상 작다. 다양한 미들웨어를 지원하고, 그 중에서 zustand의 middleware인 persist를 사용하면, 저장한 state 값을 local, session에 간단하게 저장 할 수 있다.

zustand persist의 github example

middleware of persist in zustand docs

create의 구조

persist 는 미들웨어로서 create의 콜백으로 넣게 되어 기본적으로 위에서 코드에서 본것과 같이 콜백함수로 set과 get이 인자로 있는 함수대신 넣는 것으로 이해하였다.

그 이유는 type들을 찾아본 결과 그리고, 미들웨어인 점을 종합적으로 생각해보면 create 안에 persist 콜백을 넣어 주기 때문이다.

// Create의 type 
type Create = {
<T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, [], Mos>): UseBoundStore<Mutate<StoreApi<T>, Mos>>;
<T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>
  
  // 인자타입과 반환되는 타입
(initializer: StateCreator<T, [], Mos>) => UseBoundStore<Mutate<StoreApi<T>, Mos>>;
    /**
     * @deprecated Use `useStore` hook to bind store
     */
    <S extends StoreApi<unknown>>(store: S): UseBoundStore<S>;  
}
declare const _default: Create; 

// initializer : StateCreator<T,[],Mos>
export type StateCreator<T, Mis extends [StoreMutatorIdentifier, unknown][] = [], 
                    	Mos extends [StoreMutatorIdentifier, unknown][] = [], U = T> =
((setState: Get<Mutate<StoreApi<T>, Mis>, 'setState', never>,
 getState: Get<Mutate<StoreApi<T>, Mis>, 'getState', never>, 
 store: Mutate<StoreApi<T>, Mis>) => U) & {
    $$storeMutators?: Mos;
};
Create = (initializer:((setState,getState,store)=>({useBoundStore}))

이렇게 간단히 축약 할 수 있는데

persist 구조

persist와 같은 경우

// persist type
import { persist } from "zustand/middleware";
type Persist = <T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = [], U = T>
(initializer: StateCreator<T, [...Mps, ['zustand/persist', unknown]], Mcs>, 
options: PersistOptions<T, U>) 
=> StateCreator<T, Mps, [['zustand/persist', U], ...Mcs]>;

// psersist의 options type
export interface PersistOptions<S, PersistedState = S> {
  /** Name of the storage (must be unique) */
  name: string;
  /**
   * @deprecated Use `storage` instead.
   * A function returning a storage.
   * The storage must fit `window.localStorage`'s api (or an async version of it).
   *
   * @default () => localStorage
   */
  getStorage?: () => StateStorage;
  /**
   * @default JSON.stringify
   */
  serialize?: (state: StorageValue<S>) => string | Promise<string>;
  /**
   * @param str The storage's current value.
   * @default JSON.parse
   */
  deserialize?: (str: string) => StorageValue<PersistedState> | Promise<StorageValue<PersistedState>>;
  /**
   *
   * @default createJSONStorage(() => localStorage)
   */
  storage?: PersistStorage<PersistedState> | undefined;
  /**
   * Filter the persisted value.
   *
   * @params state The state's value
   */
  partialize?: (state: S) => PersistedState;
}
Persist = 
(initializer:((setState,getState,store)=>({useBoundStore}),
persistOption:{
  name:string, // must be writing
  // others optional
  }) => StateCreator<T, Mps, [['zustand/persist', U], ...Mcs]>

이렇게 persist는 StateCreator 콜백함수를 첫번째 인자 값으로 가지고,
두 번째 인자값으로 option으로 객체가 들어가는데 { name:string}을 넣어줘야 사용 할 수 있다.
그리고 저장 할 storage는 기본적으로 localStorage 이지만, session으로 바꾸고 싶다면, storage 의 value를 바꿔주면 된다.

storage 예시

zustand에서는 persist 미들웨어를 사용하여 storage에 저장할 때 기본적으로 localStorage에 저장되게 설정 되어있다.

import { persist } from "zustand/middleware";
const useAuthStore = create<I_Auth>()(
  persist(
    (set, get,store) => ({
      email: null,
      token: null,
      setAuth() {
        set((state) => ({
          ...initialValue,
        }));
      },
    }),
    {
      name: "token",
    },
  ),
);

코드처럼 create의 인자로 persist 를 import 하여 넣어주면 된다.
그리고 나서 원하는 Component에 랩핑해주면 된다.

//...생략
  const store = useAuthStore();
  useEffect(() => {
    store.setAuth();
  }, []);

session storage 저장하기

default를 바꿔, session에 저장하게 해줘보자

import { createJSONStorage, persist } from "zustand/middleware";
persist( ..., { 
      name: "token",
      storage: createJSONStorage(() => sessionStorage),// localStorage || others 
    },

바꿔주고 싶으면 createJSONStorage 를 import하여 위와 같이 해주면 session storage에 저장된다.

value & action 분류하기

const useAuthStore = create<I_Auth>()(
  persist(
    (set, get) => ({
      email: null,
      token: null,
    }),
    {
      name: "token",
      storage: createJSONStorage(() => sessionStorage),
    },
  ),
);
// action
export const setAuthInSession = (auth: I_Auth) =>
useAuthStore.setState((state) => ({
  ...state,
  ...auth,
}));
export const resetAuthValue = ()=>useAuthStore.setState(state=>({
  ...initialValue
}))
//... other action func

// value
export const getAuthValue = <T extends keyof I_Auth>(value: T) => useAuthStore.getState()[value];

persist.method

조금더 나아가서 persist의 메소드도 간단하게 확인해보자

// persist의 property type들
(property) persist: {
    setOptions: (options: Partial<PersistOptions<I_Auth, unknown>>) => void;
    clearStorage: () => void;
    getOptions: () => Partial<PersistOptions<I_Auth, unknown>>;
    //... other options
}

persist.setOptions({ ... }) // 부분 적용가능 
persist.getOptions() // 전체 options들 읽기
persist.clearStorage() // 저장된 storage값 delete


마무리 & 내가 작성한 코드

const useAuthStore = create<I_Auth>()(
  persist(
    (_) => ({
      email: null,
      token: null,
    }),
    {
      name: "token",
      storage: createJSONStorage(() => sessionStorage),
    },
  ),
);


export const setAuthInSession = (auth: I_Auth) =>
useAuthStore.setState((state) => ({
  ...state,
  ...auth,
}));
export const resetAuthValue = ()=>useAuthStore.setState(state=>({
  ...initialValue
}))
export const getAuthValue = <T extends keyof I_Auth>(value: T) =>
  useAuthStore.getState()[value];
export default useAuthStore;
        
// tsx에서 value값 불러오기
        
//... 생략
const value = useAuthStore(state=>state.value)
const value = getAuthValue('value') 

getAuthValue 는 타입스크립트 제네릭을 연습하기 위해서 작성 해봤다.

물론 value 값은 위에서도 작성했다 싶이 둘 중 하나를 선택해도 문제가 없다.
그러나 둘 중에 선택을 무얼 할지 모르겠다면,
코드 컨벤션에 따라서 하면 좋을 것 같다는 생각이 든다.
누구는 전역상태관리에서 가져온 state인지, 일반 값인지를 모를 수도 있기 때문에

우리 프로젝트에서는 전자로 해서 value값을 가져오는 거루 하였다.


이렇게 해서 session storage에 토큰 값을 저장하는 기능을 알아보았고, 그렇다면 이걸 다른 프론트엔드 개발자들이 사용 할 수 있게 모듈화를 생각해 볼 수 있고, 페이지 조건부처리를 어떻게 할지 를 고민해봐야 한다

다음 포스팅에서 이야기를 이어나가보자

0개의 댓글

관련 채용 정보