Slices Pattern

김동현·2026년 3월 4일

안녕하세요! 프론트엔드 개발자 여러분. 오늘은 Zustand를 실무에서 더 강력하고 깔끔하게 쓸 수 있도록 만들어주는 Slices Pattern(슬라이스 패턴)에 대해 함께 공부해볼 거예요. 공식 문서를 바탕으로 차근차근, 그리고 제 실무 경험을 듬뿍 담아 부연 설명도 해드릴 테니 잘 따라와 주세요!


Slices Pattern (슬라이스 패턴)


Slicing the store into smaller stores (스토어를 더 작은 스토어들로 쪼개기)

앱에 기능이 하나둘씩 추가되다 보면, 하나의 스토어가 감당할 수 없을 만큼 점점 커지고 유지보수하기 너무 힘들어지는 순간이 오죠. 파일 하나에 코드가 천 줄, 이천 줄 넘어간다고 상상해 보세요. 끔찍하죠? 😅

이럴 때 Zustand에서는 메인 스토어를 더 작고 독립적인 개별 스토어들로 나누어서 모듈화(modularity)를 달성할 수 있어요. 게다가 이 과정이 아주 간단하답니다!

Redux를 써보신 분들이라면 Redux Toolkit의 createSlice 기능이 떠오르실 텐데요, 개념적으로 아주 비슷해요. 관심사별로(예: 유저 정보, 장바구니 상태 등) 상태와 액션을 분리하는 거죠.

먼저, 첫 번째 개별 스토어(슬라이스)를 만들어볼게요. 여기서는 '물고기' 상태를 관리합니다:

export const createFishSlice = (set) => ({
  fishes: 0,
  addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})

그다음, 또 다른 개별 스토어를 만들어볼게요. 이번엔 '곰' 상태를 관리합니다:

export const createBearSlice = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
  eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})

자, 이제 이렇게 따로따로 만든 스토어들을 하나의 바운드(결합된) 스토어(bounded store)로 합쳐줄 수 있어요.

💡 강사의 부연 설명:
여기서 create 함수 안에 들어가는 (...a)는 Zustand의 set, get, api 메서드들을 배열 형태로 받는 Rest 파라미터예요. 전개 연산자(...)를 써서 이 메서드들을 각 슬라이스 함수(createBearSlice, createFishSlice)에 고스란히 전달해 주는 원리랍니다. 정말 우아한 패턴이죠?

import { create } from 'zustand'
import { createBearSlice } from './bearSlice'
import { createFishSlice } from './fishSlice'

export const useBoundStore = create((...a) => ({
  ...createBearSlice(...a),
  ...createFishSlice(...a),
}))

Usage in a React component (React 컴포넌트에서 사용하기)

합쳐진 스토어를 React 컴포넌트에서 사용하는 방법은 기존과 완전히 똑같아요!

import { useBoundStore } from './stores/useBoundStore'

function App() {
  const bears = useBoundStore((state) => state.bears)
  const fishes = useBoundStore((state) => state.fishes)
  const addBear = useBoundStore((state) => state.addBear)
  return (
    <div>
      <h2>Number of bears: {bears}</h2>
      <h2>Number of fishes: {fishes}</h2>
      <button onClick={() => addBear()}>Add a bear</button>
    </div>
  )
}

export default App

🔥 강사의 실무 꿀팁:
컴포넌트에서 상태를 꺼내올 때, const { bears, fishes } = useBoundStore() 처럼 구조 분해 할당을 쓰고 싶어질 수 있어요. 하지만 위 코드처럼 상태를 하나하나 개별적인 선택자(selector)로 꺼내오는 것을 강력히 권장합니다! 구조 분해 할당을 사용하면 스토어 내의 어떤 상태가 변하든 컴포넌트가 리렌더링 될 위험이 크거든요. 개별적으로 가져오면 딱 내가 구독한 상태(bearsfishes)가 변할 때만 리렌더링 되기 때문에 성능 최적화에 훨씬 유리합니다.


Updating multiple stores (여러 스토어 동시에 업데이트하기)

개발을 하다 보면, 한 번의 액션으로 여러 슬라이스의 상태를 동시에 바꿔야 할 때가 있어요. Zustand에서는 단일 함수 안에서 동시에 여러 스토어를 업데이트하는 것도 가능하답니다.

예를 들어, '곰'과 '물고기'를 동시에 추가하는 액션을 만들어 볼게요. 인자로 get을 받아오면 다른 슬라이스의 액션에 쉽게 접근할 수 있어요!

export const createBearFishSlice = (set, get) => ({
  addBearAndFish: () => {
    get().addBear()
    get().addFish()
  },
})

이 새로운 슬라이스도 앞서 했던 방식 그대로 결합된 스토어에 합쳐주기만 하면 됩니다.

import { create } from 'zustand'
import { createBearSlice } from './bearSlice'
import { createFishSlice } from './fishSlice'
import { createBearFishSlice } from './createBearFishSlice'

export const useBoundStore = create((...a) => ({
  ...createBearSlice(...a),
  ...createFishSlice(...a),
  ...createBearFishSlice(...a),
}))

Adding middlewares (미들웨어 추가하기)

Zustand의 꽃 중 하나가 바로 미들웨어죠! 결합된 스토어에 미들웨어를 추가하는 방법도 일반적인 단일 스토어에 추가할 때와 동일해요.

우리가 만든 useBoundStore에 데이터를 로컬 스토리지에 저장해주는 persist 미들웨어를 추가해 볼까요?

import { create } from 'zustand'
import { createBearSlice } from './bearSlice'
import { createFishSlice } from './fishSlice'
import { persist } from 'zustand/middleware'

export const useBoundStore = create(
  persist(
    (...a) => ({
      ...createBearSlice(...a),
      ...createFishSlice(...a),
    }),
    { name: 'bound-store' },
  ),
)

🚨 강사의 강력한 주의사항:
공식 문서에서도 강조하고 있지만, 미들웨어는 반드시 최종적으로 합쳐진 스토어(combined store)에만 적용하셔야 해요. 개별 슬라이스(createBearSlice 등) 내부에 미들웨어를 씌우려고 하면 타입이 꼬이거나 상태 동기화가 깨지는 등 전혀 예상치 못한 버그가 발생할 수 있습니다. 꼭 명심해 주세요!


Usage with TypeScript (TypeScript와 함께 사용하기)

실무에서는 당연히 TypeScript를 쓰시겠죠? Zustand에서 TypeScript와 함께 슬라이스 패턴을 사용하는 방법에 대한 상세한 가이드는 여기에서 확인하실 수 있어요.

💡 강사의 TypeScript 꿀팁:
각 슬라이스가 반환하는 상태와 액션의 타입을 interfacetype으로 명확히 지정해 두고, 스토어를 결합할 때 교차 타입(Intersection Type, &)을 사용해 BearSlice & FishSlice처럼 타입을 합쳐주면 자동 완성과 타입 추론을 완벽하게 누릴 수 있습니다. 위 링크된 공식 문서를 꼭! 한 번 정독해 보시길 추천합니다.


이전 글: 스토어 액션 없이 연습하기 (Practice with no store actions)
다음 글: 불변 상태와 병합 (Immutable state and merging)

profile
프론트에_가까운_풀스택_개발자

0개의 댓글