Initialize state with props

김동현·2026년 3월 4일

zustand 공식문서 번역

목록 보기
16/19

props로 상태 초기화하기 (Initialize state with props)

컴포넌트에서 전달받은 props로 스토어를 초기화해야 하는 등 의존성 주입(Dependency injection)이 필요한 경우가 있죠? 이럴 때는 Zustand의 바닐라(vanilla) 스토어와 React.context를 함께 사용하는 방식을 권장합니다.

💡 강사의 팁 & 보충 설명:
여러분, 보통 Zustand를 사용할 때는 전역 스토어를 하나 툭 만들어서 어디서든 불러 쓰잖아요? 그런데 컴포넌트나 특정 페이지마다 서로 다른 초기값을 가져야 한다면 어떨까요? 혹은 서버 사이드 렌더링(SSR) 환경에서 부모 컴포넌트가 API를 호출해 얻은 데이터를 스토어의 초기값으로 밀어 넣어야 한다면요?

특히 여러분이 공부하셨던 Next.js 같은 환경에서는 이 패턴이 정말 핵심입니다. 전역 스토어 하나만 쓰게 되면 서버에서 여러 유저 간에 상태가 공유되어 버리는 치명적인 버그가 생길 수 있거든요. 스토어 인스턴스를 격리시켜서 하위 컴포넌트 트리에 주입해주는 이 Context 패턴을 꼭 익혀두세요!


createStore를 사용한 스토어 생성 (Store creator with createStore)

import { createStore } from 'zustand'

interface BearProps {
  bears: number
}

interface BearState extends BearProps {
  addBear: () => void
}

type BearStore = ReturnType<typeof createBearStore>

const createBearStore = (initProps?: Partial<BearProps>) => {
  const DEFAULT_PROPS: BearProps = {
    bears: 0,
  }
  return createStore<BearState>()((set) => ({
    ...DEFAULT_PROPS,
    ...initProps,
    addBear: () => set((state) => ({ bears: ++state.bears })),
  }))
}

💡 강사의 보충 설명:
코드를 자세히 보시면 평소에 쓰던 create가 아니라 createStore를 사용하고 있죠? create는 리액트 훅을 반환하지만, 바닐라 함수인 createStore는 스토어 객체 그 자체를 반환합니다. 우리는 이 스토어 객체를 만들어서 아래에서 배울 React Context에 쏙 담아줄 거예요.


React.createContext로 컨텍스트 생성하기 (Creating a context with React.createContext)

import { createContext } from 'react'

export const BearContext = createContext<BearStore | null>(null)

기본적인 컴포넌트 사용법 (Basic component usage)

// Provider implementation
import { useState } from 'react'

function App() {
  const [store] = useState(() => createBearStore())
  return (
    <BearContext.Provider value={store}>
      <BasicConsumer />
    </BearContext.Provider>
  )
}

💡 강사의 면접 대비 팁:
여기서 정말 중요한 포인트가 하나 등장합니다! App 컴포넌트에서 createBearStore()를 호출할 때 왜 굳이 useState의 콜백 함수로 넣었을까요?
바로 컴포넌트가 리렌더링 될 때마다 스토어가 불필요하게 재생성되는 것을 막기 위해서입니다. 이렇게 지연 초기화(lazy initialization)를 해주면 컴포넌트가 처음 마운트되는 시점에 딱 한 번만 스토어가 생성됩니다. 프론트엔드 면접에서 기술 질문으로 훅의 지연 초기화나 최적화 관련 질문이 나왔을 때, 이런 사례를 들어 답변하시면 아주 훌륭한 인상을 줄 수 있습니다.

// Consumer component
import { useContext } from 'react'
import { useStore } from 'zustand'

function BasicConsumer() {
  const store = useContext(BearContext)
  if (!store) throw new Error('Missing BearContext.Provider in the tree')

  const bears = useStore(store, (s) => s.bears)
  const addBear = useStore(store, (s) => s.addBear)

  return (
    <>
      <div>{bears} Bears.</div>
      <button onClick={addBear}>Add bear</button>
    </>
  )
}

자주 쓰이는 패턴들 (Common patterns)

컨텍스트 Provider 감싸기 (Wrapping the context provider)

// Provider wrapper
import { useState } from 'react'

type BearProviderProps = React.PropsWithChildren<BearProps>

function BearProvider({ children, ...props }: BearProviderProps) {
  const [store] = useState(() => createBearStore(props))
  return <BearContext.Provider value={store}>{children}</BearContext.Provider>
}

컨텍스트 로직을 커스텀 훅으로 분리하기 (Extracting context logic into a custom hook)

// Mimic the hook returned by `create`
import { useContext } from 'react'
import { useStore } from 'zustand'

function useBearContext<T>(selector: (state: BearState) => T): T {
  const store = useContext(BearContext)
  if (!store) throw new Error('Missing BearContext.Provider in the tree')
  return useStore(store, selector)
}
// Consumer usage of the custom hook
function CommonConsumer() {
  const bears = useBearContext((s) => s.bears)
  const addBear = useBearContext((s) => s.addBear)
  return (
    <>
      <div>{bears} Bears.</div>
      <button onClick={addBear}>Add bear</button>
    </>
  )
}

💡 강사의 실무 팁:
이 패턴은 실무에서 협업할 때 정말 빛을 발합니다. 스토어 상태를 가져다 쓰는 다른 개발자는 내부적으로 Context가 어떻게 구성되었는지 알 필요 없이, 단순히 useBearContext라는 커스텀 훅만 호출하면 되거든요. 완벽한 캡슐화죠!
나중에 취업을 위해 준비 중이신 블로그 뷰어나 AI 기반 웹 애플리케이션 같은 포트폴리오를 만드실 때, 초기 데이터(예: 서버에서 받아온 초기 게시글 정보나 번역 설정값)를 Zustand 스토어에 안전하게 주입해야 할 일이 생길 텐데, 이때 이 구조를 적용해 두시면 코드의 퀄리티와 유지보수성이 확 올라갑니다.


(선택 사항) 안정적인 출력을 위해 메모이제이션된 선택자 사용하기 (Optionally use memoized selector for stable outputs)

import { useShallow } from 'zustand/react/shallow'

const meals = ['Salmon', 'Berries', 'Nuts']

function CommonConsumer() {
  const bearMealsOrder = useBearContext(
    useShallow((s) =>
      Array.from({ length: s.bears }).map((_, index) =>
        meals.at(index % meals.length),
      ),
    ),
  )
  return (
    <>
      Order:
      <ul>
        {bearMealsOrder.map((meal) => (
          <li key={meal}>{meal}</li>
        ))}
      </ul>
    </>
  )
}

(선택 사항) 커스텀 동등성 검사 함수 사용 허용하기 (Optionally allow using a custom equality function)

// Allow custom equality function by using useStoreWithEqualityFn instead of useStore
import { useContext } from 'react'
import { useStoreWithEqualityFn } from 'zustand/traditional'

function useBearContext<T>(
  selector: (state: BearState) => T,
  equalityFn?: (left: T, right: T) => boolean,
): T {
  const store = useContext(BearContext)
  if (!store) throw new Error('Missing BearContext.Provider in the tree')
  return useStoreWithEqualityFn(store, selector, equalityFn)
}

전체 예제 (Complete example)

// Provider wrapper & custom hook consumer
function App2() {
  return (
    <BearProvider bears={2}>
      <HookConsumer />
    </BearProvider>
  )
}

Edit this page

이전 (Previous): SSR and Hydration
다음 (Next): Testing

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

0개의 댓글