이번에는 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}),
}))