zustand 상태관리 방식을 이해한다는 것은 무엇일까? 에 대한 고민
- ⇒ create 와 createStore 함수 차이점으로부터 분석해보기
zustand 상태관리를 이해하기 위해서는 우선 아래 네 가지 개념들의 유기적 상관관계를 이해하면 됨
createStore
create
useSyncExternalStore
useStore
-
전역상태(zustand): 전역상태로 관리할 사용자정의 initialState(State & Action) + StoreApi ( setState, getState, getInitialState, subscribe )를 가진 객체
- 👉 "React 렌더링 사이클과 독립적으로 존재. “
-
createStore : 전역상태(사용자정의 initialState + StoreAPI) 객체를 만들어준다.
- 👉 ”상태 관리를 위한 핵심 API인 StoreApi ****( setState, getState, getInitialState, subscribe )를 생성하고, 이를 사용자가 정의한 State 및 Action(메서드)와 결합하여 완전한 전역상태 객체를 반환”
-
create : 내부적으로 전역상태를 만들고, 그 전역상태를 구독가능한 Hook까지 반환하는 함수
- 👉 "createStore를 호출하여 스토어를 생성하고, 이를 내부적으로 useStore 훅으로 래핑하여 해당 Store에 Bind된 새로운 useStore Hook 반환”
- // useBoundStore(selector)
-
useSyncExternalStore : 리액트 시스템 외부에 존재하는 zustand 전역상태와 리액트를 연결하는 기능 (실제 프로덕션 코드상에서 사용하게 될 일은 거의 없음, 전역상태 라이브러리 메인테이너용)
-
useStore : 전역상태에서 사용자정의 상태의 특정 부분( State / Action )을 select하여 사용
- selector 반환값을 object.is 메서드로 검사하여 리렌더를 결정 (전역상태를 기본적으로 구독)
- create사용시와 달리 대상 target인 Store를 함께 주입해주어야함.
export const createStore = (
(createState) => createState
? createStoreImpl(createState) // 케이스 1: 직접 콜백 전달
: createStoreImpl // 케이스 2: createStoreImpl 함수 반환
) as CreateStore
- 콜백을 넣고 바로 호출할지
- createStore( callbackFn ) : store
- 콜백없이 호출하고 그다음 콜백을 넣어서 호출할지
- createStore()( callbackFn) : store
- 결국 동일한
createStoreImpl(createState)
결과값을 가진다.
const createStoreImpl: CreateStoreImpl = (createState) => {
type TState = ReturnType<typeof createState>
type Listener = (state: TState, prevState: TState) => void
**let state: TState // 1**
const listeners: Set<Listener> = new Set()
// 2
const setState: StoreApi<TState>['setState'] = (partial, replace) => {
const nextState =
typeof partial === 'function'
? (partial as (state: TState) => TState)(state)
: partial
if (!Object.is(nextState, state)) {
const previousState = state
state =
(replace ?? (typeof nextState !== 'object' || nextState === null))
? (nextState as TState)
: Object.assign({}, state, nextState)
listeners.forEach((listener) => listener(state, previousState))
}
}
// 3
const getState: StoreApi<TState>['getState'] = () => state
// 4
const getInitialState: StoreApi<TState>['getInitialState'] = () =>
initialState
// 5
const subscribe: StoreApi<TState>['subscribe'] = (listener) => {
listeners.add(listener)
// Unsubscribe
return () => listeners.delete(listener)
}
// 6
const api = { setState, getState, getInitialState, subscribe }
// 7
const initialState = (state = createState(setState, getState, api))
return api as any
}
- TDZ 문제(초기화 전 접근)는 결국 런타임 접근 가능 제한 여부에 관한 문제
- getInitialState함수의 호출은 initialState함수 호출이후에 호출되는 구조로 강제되어있음.
- 전역상태를 만들어줌
- 그런데 사용자 정의 상태와, zustand가 정의하는 Store API를 곁들인 ,,
- initialState + StoreAPI
react 상태관리 시스템 바깥에서 전역스토어를 초기화시켜주고 싶은 경우
ex) 서버사이드렌더링시, 서버에서 fetch한 데이터 기반으로 initialState를 초기화해주고, 이렇게 만든 Store객체를 Context Provider value로 제공하여, Context API와 useStore훅을 함께 사용하는 방식으로 전역상태를 제공
export const create = (<T>(createState: StateCreator<T, [], []> | undefined) =>
createState ? createImpl(createState) : createImpl) as Create
- create(createState)
- create()(createState)
const createImpl = <T>(createState: StateCreator<T, [], []>) => {
const api = createStore(createState) // 전역 store생성
const useBoundStore: any = (selector?: any) => useStore(api, selector)
Object.assign(useBoundStore, api)
return useBoundStore
}
export function useStore<TState, StateSlice>(
api: ReadonlyStoreApi<TState>, // 전역store객체
selector: (state: TState) => StateSlice = identity as any, // select
) {
const slice = React.useSyncExternalStore(
api.subscribe,
() => selector(api.getState()),
() => selector(api.getInitialState()),
)
React.useDebugValue(slice)
return slice
}
- create함수는 인자값으로 , createState콜백을 하나 전달받음
- createState콜백: 클로저로 전역상태의 setter,getter등의 메서드를 전달받고, 이를 활용해 initialState로 사용자 정의 State & Action을 정의할 수 있도록 도와줌
- create함수 호출 반환값은 createStore로 만든 Store에 바인딩된 새로운 useStore훅을 반환
- 해당 hook에 selector만을 제공하여 사용자 정의 상태를 가져옴
리액트 애플리케이션
┌─────────────────────────────────────────┐
│ │
│ 컴포넌트 트리 │
│ └─────────────────────────────────┘ │
│ ↕ │
│ 상태 구독/사용 │
│ ↕ │
└────────────────|────────────────────────┘
↕
외부 전역 상태 관리 시스템
┌────────────────|────────────────────────┐
│ ┌──────────────────────┐ │
│ │ 상태 데이터 │ │
│ │ { │ │
│ │ user: User, │ │
│ │ theme: string, │ │
│ │ count: number │ │
│ │ } │ │
│ └──────────────────────┘ │
│ ↕ │
│ ┌──────────────────────┐ │
│ │ 제어 API들 │ │
│ │ - setState() │ │
│ │ - getState() │ │
│ │ - subscribe() │ │
│ │ - destroy() │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────┘
-
zustand에서 만드는 전역상태는 사용자 정의 state와 StoreAPI를 보유한 Object이며, 이는 React 애플리케이션 외부에 존재한다.
-
create 방식 사용시
- 전역상태를 생성한다.
- useSyncExternalStore가 이 전역상태객체와 React시스템을 연결한다.
- create함수 반환값인 Hook으로 전역상태를 소비한다.
-
createStore를 직접 사용하여 Provider 방식으로 사용할 수도 있다.
-
이제 React에서 전역상태를 관리할 수 있다.
-
create를 사용한다면 Providerless하게,
-
creatreStore를 직접 사용한다면 Provider와 함께