Redux를 사용하다가 새로운 상태관리 라이브러리도 사용해 보고 싶어 zustand라는것을 공부해보았습니다.
Zustand는 React를 위한 작고 빠르고 확장 가능한 상태 관리 라이브러리입니다. Redux나 MobX와 달리 보일러플레이트가 거의 없고 TypeScript 지원이 우수합니다.
yarn add zustand
import { create } from 'zustand'
interface CounterState {
count: number
increment: () => void
decrement: () => void
reset: () => void
}
export const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}))
import { useCounterStore } from './store/useCounterStore'
function Counter() {
const { count, increment, decrement, reset } = useCounterStore()
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
)
}
로컬 스토리지에 상태를 저장하려면 persist 미들웨어를 사용합니다:
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface UserState {
user: User | null
isLoggedIn: boolean
login: (user: User) => void
logout: () => void
}
export const useUserStore = create<UserState>()(
persist(
(set) => ({
user: null,
isLoggedIn: false,
login: (user) => set({ user, isLoggedIn: true }),
logout: () => set({ user: null, isLoggedIn: false }),
}),
{
name: 'user-storage', // localStorage 키 이름
}
)
)
특정 상태만 구독하여 불필요한 리렌더링을 방지할 수 있습니다:
// 전체 상태를 구독
const { count, increment } = useCounterStore()
// 특정 상태만 구독
const count = useCounterStore((state) => state.count)
const increment = useCounterStore((state) => state.increment)
// 여러 상태를 구독
const { count, name } = useCounterStore((state) => ({
count: state.count,
name: state.name,
}))
// 직접 값 설정
set({ count: 10 })
// 이전 상태를 기반으로 업데이트
set((state) => ({ count: state.count + 1 }))
// 부분 업데이트
set((state) => ({ user: { ...state.user, name: 'New Name' } }))
// 비동기 업데이트
const fetchUser = async () => {
const user = await api.getUser()
set({ user })
}
export const useStore = create((set, get) => ({
count: 0,
increment: () => {
const currentCount = get().count
set({ count: currentCount + 1 })
},
reset: () => set({ count: 0 }),
}))
로컬 스토리지에 상태를 저장합니다.
Redux DevTools와 연동합니다.
import { devtools } from 'zustand/middleware'
export const useStore = create(
devtools(
(set) => ({
// 스토어 로직
}),
{ name: 'Store Name' }
)
)
불변성을 쉽게 관리할 수 있습니다.
import { immer } from 'zustand/middleware/immer'
export const useStore = create(
immer((set) => ({
users: [],
addUser: (user) =>
set((state) => {
state.users.push(user)
}),
}))
)
interface State {
count: number
increment: () => void
}
// 타입 안전성 보장
export const useStore = create<State>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
관심사별로 스토어를 분리하세요:
// userStore.ts
export const useUserStore = create<UserState>(...)
// cartStore.ts
export const useCartStore = create<CartState>(...)
// settingsStore.ts
export const useSettingsStore = create<SettingsState>(...)
액션 함수명을 명확하게 작성하세요:
// 좋은 예
const increment = () => set((state) => ({ count: state.count + 1 }))
const addToCart = (item: Item) => set((state) => ({ cart: [...state.cart, item] }))
// 피해야 할 예
const update = () => set(...)
const change = () => set(...)
복잡한 상태는 정규화하여 관리하세요:
interface State {
users: Record<string, User>
posts: Record<string, Post>
userPosts: Record<string, string[]>
}
// 전체 스토어를 구독하지 말고 필요한 부분만 구독
const count = useStore((state) => state.count)
const increment = useStore((state) => state.increment)
import { useMemo } from 'react'
const expensiveValue = useMemo(() => {
return useStore.getState().items.filter(item => item.active)
}, [])
음...신세계인데..? 너무 설정하는게 편하네여 redux는 store.ts나 provider.tsx로 설정할게 많은데 그냥 이건 딸깍하고 조금 더 공부하면 편리하게 사용가능 할 꺼 같습니다...ㅎㅎ
Redux를 사용하면 상태의 중앙 집중화가 되기 때문에 프로젝트의 확상성, 상태값 재사용성이 한 곳에서 관리하여 유지모수가 용이 해서 큰 규모의 프로젝트에 맞는 강력한 상태 관리 라이브러리이고 Zustand는 좀 가벼운 상태 관리 라이브러리여서 작은 규모에 적합할 꺼 같은 느낌입니다.