이전 포스팅에서
- 전역적으로 사용할 것이므로, 전역관리 상태를 사용하여햐 했고,
- 혹시나 하는 새로고침에도, 값이 사라지지 않아야 하므로,
localStorage, SessionStorage중 하나에 저장해서 토큰 값을 받아오면 우선적으로 브라우저의 스토리지에 저장하고 그 값을 전역에 저장하여서 관리하기로 생각하였다.
위의 두가지를 사용해서 토큰을 전역하기에 zustand의 미들웨어인 persist
를 사용하여 토큰관리를 하기로 하였다.
우선 persist의 기초 로직과 그리고 내가 어떻게 작성했는지를 알아보자.
지금 작성하는 글은 타입스크립트와 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
함수를 호출하면 콜백함수가 들어가게 되는데, 콜백함수 안에는 set
과 get
이라는 콜백함수 파라미터가 있다.
set
을 호출하면 set의 콜백함수안에는 useCount
에 저장되어있는 value
와 action
정보가 담겨있다. get
을 호출하면 useCount
에 저장되어있는 value
와 action
이 담겨있다. 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는 redux와 유사한 flux pattern이며, 용량 자체도 redux대비 10배이상 작다. 다양한 미들웨어를 지원하고, 그 중에서 zustand의 middleware인 persist
를 사용하면, 저장한 state 값을 local, session에 간단하게 저장 할 수 있다.
zustand persist의 github example
middleware of persist in zustand docs
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 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를 바꿔주면 된다.
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();
}, []);
default를 바꿔, session에 저장하게 해줘보자
import { createJSONStorage, persist } from "zustand/middleware";
persist( ..., {
name: "token",
storage: createJSONStorage(() => sessionStorage),// localStorage || others
},
바꿔주고 싶으면 createJSONStorage
를 import하여 위와 같이 해주면 session storage에 저장된다.
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의 메소드도 간단하게 확인해보자
// 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값을 가져오는 거루 하였다.
다음 포스팅에서 이야기를 이어나가보자