icon안동민 개발노트

Zustand를 활용한 상태 관리


 Zustand는 간단하고 빠른 상태 관리 라이브러리입니다.

 React의 다른 상태 관리 솔루션들과 비교하여 보일러플레이트가 적고 사용이 쉽다는 장점이 있습니다.

 TypeScript와 함께 사용하면 타입 안전성을 보장하면서 효율적인 상태 관리가 가능합니다.

Zustand의 기본 개념과 차이점

 Zustand는 단일 스토어를 사용하며, 리듀서나 액션 타입 정의 없이 직접 상태를 변경할 수 있습니다.

 Redux와 달리 불변성을 자동으로 처리하며, Context API보다 성능이 우수합니다.

Zustand 스토어 정의 및 사용

 TypeScript로 Zustand 스토어 정의하기

import create from 'zustand'
 
interface BearState {
  bears: number
  increase: (by: number) => void
}
 
const useBearStore = create<BearState>((set) => ({
  bears: 0,
  increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
 
// 사용
function BearCounter() {
  const bears = useBearStore((state) => state.bears)
  return <h1>{bears} around here...</h1>
}
 
function Controls() {
  const increase = useBearStore((state) => state.increase)
  return <button onClick={() => increase(1)}>one up</button>
}

슬라이스 패턴 구현

 대규모 애플리케이션을 위한 슬라이스 패턴

import create from 'zustand'
 
interface BearSlice {
  bears: number
  increase: (by: number) => void
}
 
interface FishSlice {
  fishes: number
  catch: (amount: number) => void
}
 
const createBearSlice = (set) => ({
  bears: 0,
  increase: (by) => set((state) => ({ bears: state.bears + by })),
})
 
const createFishSlice = (set) => ({
  fishes: 0,
  catch: (amount) => set((state) => ({ fishes: state.fishes + amount })),
})
 
const useStore = create<BearSlice & FishSlice>((set) => ({
  ...createBearSlice(set),
  ...createFishSlice(set),
}))
 
// 사용
function BearFishCounter() {
  const { bears, fishes } = useStore()
  return <h1>{bears} bears and {fishes} fishes</h1>
}

Zustand와 React 훅 조합

 성능 최적화를 위한 훅 조합

import { useMemo } from 'react'
import { useStore } from 'zustand'
 
function BearCounter() {
  const bears = useStore((state) => state.bears)
  const doubledBears = useMemo(() => bears * 2, [bears])
  return <h1>{doubledBears} bears</h1>
}

Zustand 미들웨어 시스템

 로깅 미들웨어 구현

import create from 'zustand'
import { devtools } from 'zustand/middleware'
 
interface BearState {
  bears: number
  increase: (by: number) => void
}
 
const useBearStore = create<BearState>()(
  devtools(
    (set) => ({
      bears: 0,
      increase: (by) => set((state) => ({ bears: state.bears + by })),
    })
  )
)

비동기 액션 및 부수 효과 관리

 타입 안전한 비동기 액션

import create from 'zustand'
 
interface BearState {
  bears: number
  fetchBears: () => Promise<void>
}
 
const useBearStore = create<BearState>((set) => ({
  bears: 0,
  fetchBears: async () => {
    const response = await fetch('api/bears')
    const bears = await response.json()
    set({ bears })
  },
}))
 
// 사용
function BearComponent() {
  const { bears, fetchBears } = useBearStore()
  
  useEffect(() => {
    fetchBears()
  }, [])
 
  return <h1>{bears} bears</h1>
}

외부에서의 상태 접근 및 조작

 컴포넌트 외부에서 Zustand 상태 접근

import create from 'zustand'
 
interface BearState {
  bears: number
  increase: (by: number) => void
}
 
const useBearStore = create<BearState>((set) => ({
  bears: 0,
  increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
 
// 외부 접근
export const increaseBears = (by: number) => useBearStore.getState().increase(by)
 
// 사용
function ExternalControl() {
  return <button onClick={() => increaseBears(1)}>Increase bears</button>
}

Zustand 상태 관리 테스트

 Zustand 스토어 테스트

import { renderHook, act } from '@testing-library/react-hooks'
import { useBearStore } from './bearStore'
 
test('should increase bears', () => {
  const { result } = renderHook(() => useBearStore())
 
  act(() => {
    result.current.increase(1)
  })
 
  expect(result.current.bears).toBe(1)
})

Best Practices와 주의사항

  1. 상태 정규화 : 중첩된 상태보다는 정규화된 상태 구조를 사용합니다.
  2. 선택적 구독 : 필요한 상태만 선택적으로 구독하여 불필요한 리렌더링을 방지합니다.
  3. 타입 안전성 : TypeScript의 타입 시스템을 최대한 활용하여 타입 안전성을 확보합니다.
  4. 미들웨어 활용 : 로깅, 영속성 등을 위해 미들웨어를 적극 활용합니다.
  5. 상태 초기화 : 필요한 경우 상태를 초기화하는 액션을 정의합니다.
  6. 불변성 유지 : Zustand가 자동으로 불변성을 처리하지만, 복잡한 상태 변경 시 주의가 필요합니다.
  7. 성능 최적화 : 큰 상태 객체를 다룰 때는 성능 최적화에 주의를 기울입니다.
  8. 테스트 작성 : 상태 로직에 대한 단위 테스트를 작성합니다.
  9. 모듈화 : 큰 스토어를 여러 작은 모듈로 분리하여 관리합니다.
  10. 문서화 : 복잡한 상태 로직이나 액션에 대해 주석으로 문서화합니다.

 Zustand는 간단하면서도 강력한 상태 관리 솔루션을 제공합니다.

 TypeScript와 함께 사용하면 타입 안전성을 보장하면서도 효율적인 상태 관리가 가능합니다. 특히 작은 규모의 프로젝트부터 대규모 애플리케이션까지 유연하게 적용할 수 있어, 다양한 상황에서 유용하게 활용될 수 있습니다.

 Zustand의 장점 중 하나는 보일러플레이트 코드가 적다는 것입니다.

 이는 개발 생산성을 높이고 코드의 가독성을 향상시킵니다.

 또한 리액트의 Context API나 Redux와 달리 성능 최적화가 기본적으로 잘 되어 있어 별도의 최적화 작업 없이도 효율적인 상태 관리가 가능합니다.