zustand(A-Z 기본심화편) #2

BuYong·2024년 4월 6일

zustand

목록 보기
3/4

이번에는 zustand를 조금 깊이 있게 사용해봅시다.
이번 내용부터는 typescript위주로 설명이 될 예정입니다.

또한, 이번 내용부터는 type을 쓰지않고 interface만 쓸껍니다. 이유는 type은 extends를 사용할 수 없기 때문입니다.

먼저 일반적인 zustand의 저장소의 구조입니다.

// src/store/user.type.ts
export interface UserState {
  id: number;
  userID: string;
  userName: string;
  age: number;
  email: string
}

export interface UserAction {
  fetchUserData: () => Promise<void>
  setUserID: (data: string) => void
  setUserName: (data: string) => void
  resetUser: () => void
}

export interface UserStore extends UserState, UserAction {}
// src/store/user.ts
import {create} from 'zustand'
import {UserState, UserStore} from 'user.type.ts'

const defaultState = {
  id: -1,
  userID: '',
  userName: '',
} as Omit<UserState, 'age' | 'email'>

export const useUserStore = create<UserStore>((set, get) => ({
  ...defaultState,
  age: 0,
  email: '',
  fetchUserData: async () => { 
    // react-query를 구현하기 싫고, axios 또는 fetch로도 충분한 경우 이렇게 사용하고
    // react-query를 사용할 경우 setUser(data:UserState 또는 사용자 정의 타입) 이런식으로 구현해서 사용하면됩니다.
    // 어느 한것에 제한되려고 하지마세요. 자신의 성장을 막는 길입니다.
    ..... // 사용자 정보 호출하는 코드
  },
  setUserID: (data) => set({userID: data}),
  setUserName: (data) => set({userName: data}),
}))

이 구현 코드에서 이상한점을 발견할 수 있습니다. 바로 fetchUserData이죠. redux를 사용하셨다면, 아마 이부분이 좀 의아할 수 있을껍니다.

👍zustand는 내부에서 동기, 비동기 전부 다 가능합니다.👍

redux에서도 async 처리가 가능하지만 createAsyncThunk를 사용해야할것이고, 제가 느끼기엔 별로 좋지 않았습니다.

또, redux의 async처리가 애매한것이 react-query를 사용하게되는 시발점이 됐죠. (처음에는 hook으로 fetch된 데이터를 사용했죠, 여기서 발전한것이 swr, react-query인것이죠. 그래서 react-query 또는 swr은 hook형태가 기본입니다 ^O^)

여기서 저는 한번 더 생각을 하게됐고 그 생각이 "zustand내부에서 서버정보를 fetch한다면, 성능상으로 훨씬좋지않나?"였고, 저는 거의 왠만한 프로젝트를 이런식으로 구현했었죠.
요즘에는 react-query로 쓰는것도 나쁘지않다는 결론이 났지만요(axios로 요청하고, 데이터를 cache를 하는것보다 react-query로 요청해서 편하게 관리하는게 좋더라구요)

react-query와 zustand를 사용하는 경우 예시코드

// src/type/user.ts
// 별도로 타입을 추가하는 이유는 UserState에서 별도로 사용하는 상태값이 있을 수 있기때문입니다.
export interface User {
  id: number;
  userID: string;
  userName: string;
}

// src/store/user.type.ts
export interface UserState {
  id: number;
  userID: string;
  userName: string;
  age: number;
  email: string
}// 이부분뿐만 아니라 아래도 가능합니다
export interface UserState extends User { // << User타입을 base로 UserState를 만든 모습이죠
  age: number;
  email: string;
}

export interface UserAction {
  setUserData: (user: User) => Promise<void>
  setUserID: (data: string) => void
  setUserName: (data: string) => void
  resetUser: () => void
}

export interface UserStore extends UserState, UserAction {}
// src/store/user.ts
import {create} from 'zustand'
import {UserState, UserStore} from 'user.type.ts'

const defaultState = {
  id: -1,
  userID: '',
  userName: '',
} as Omit<UserState, 'age' | 'email'>

export const useUserStore = create<UserStore>((set, get) => ({
  ...defaultState,
  age: 0,
  email: '',
  setUserData: async (user) => { 
    set(user) // << user의 키값이 userState의 키값과 다르다면 각각 할당해줘야합니다.
    set({id: user.id}) // << 이런식으로
    // react-query를 구현하기 싫고, axios 또는 fetch로도 충분한 경우 이렇게 사용하고
    // react-query를 사용할 경우 setUser(data:UserState 또는 사용자 정의 타입) 이런식으로 구현해서 사용하면됩니다.
    // 어느 한것에 제한되려고 하지마세요. 자신의 성장을 막는 길입니다.
    ..... // 사용자 정보 호출하는 코드
  },
  setUserID: (data) => set({userID: data}),
  setUserName: (data) => set({userName: data}),
}))
profile
코딩을 좀 더 재밌게

0개의 댓글