목차
Introduction
zustand 상태관리 방식을 이해한다는 것은 무엇일까?
zustand 상태관리를 이해하기 위해 아래 네 가지 개념들의 유기적 상관관계를 이해해보도록 한다.
- createStore
- create
- useSyncExternalStore
- useStore
Overview
- 전역상태(zustand): StoreApi ( setState, getState, getInitialState, subscribe )로 접근 가능한 캡슐화된 객체
- 👉 "React 렌더링 사이클과 독립적으로 존재. “
- createStore : 전역상태를 만들고, storeAPI를 반환한다.
- 👉 ”캡슐화된 사용자정의 State 및 Action(메서드)를 만들고, 상태 관리를 위한 핵심 API인 StoreApi ( setState, getState, getInitialState, subscribe )를 반환”
- create : 내부적으로 전역상태를 만들고, 그 전역상태를 구독가능한 Hook까지 반환하는 함수
- 👉 "createStore를 호출하여 스토어를 생성하고, 이를 내부적으로 useStore 훅으로 래핑하여 해당 Store에 Bind된 새로운 useStore Hook 반환”
- useSyncExternalStore : 리액트 시스템 외부에 존재하는 zustand 전역상태와 리액트를 연결하는 기능
- useStore : 전역상태에서 사용자정의 상태의 특정 부분( State / Action )을 select하여 사용
- selector 반환값을 object.is 메서드로 검사하여 리렌더를 결정 (전역상태를 기본적으로 구독)
- create사용시와 달리 대상 target인 Store를 함께 주입해주어야함.
Deep Dive
createStore
JAVASCRIPT
export const createStore = (
(createState) => createState
? createStoreImpl(createState) // 케이스 1: 직접 콜백 전달
: createStoreImpl // 케이스 2: createStoreImpl 함수 반환
) as CreateStore
- 콜백을 넣고 바로 호출할지
- createStore( callbackFn ) : store
- 콜백없이 호출하고 그다음 콜백을 넣어서 호출할지
- createStore()( callbackFn) : store
- 결국 동일한 createStoreImpl(createState) 결과값을 가진다.
createStoreImpl
BASH
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함수 호출이후에 호출되는 구조로 강제되어있음.
createStoreImpl 역할
- 전역상태를 만들고
- 전역상태를 컨트롤 할 수 있는 storeAPI를 외부로 반환
- 이 때, 전역상태는 캡슐화 되어 storeAPI로만 get, set이 가능하다.
createStore 함수 정리
- 캡슐화된 전역상태를 만든다.
- 전역상태에 접근가능한 storeAPI를 반환한다.
create
TYPESCRIPT
export const create = (<T>(createState: StateCreator<T, [], []> | undefined) =>
createState ? createImpl(createState) : createImpl) as Create
- create(createState)
- create()(createState)
// 위 사용법을 위한 단순한 분기처리이다.
createImpl
TYPESCRIPT
const createImpl = <T>(createState: StateCreator<T, [], []>) => {
const api = createStore(createState) // 전역 store생성, storeAPI반환
const useBoundStore: any = (selector?: any) => useStore(api, selector)
Object.assign(useBoundStore, api)
return useBoundStore
}
- useStore훅에 storeAPI를 바인딩하여 useBoundStore형태의 새로운 훅을 반환한다.
useStore
TYPESCRIPT
export function useStore<TState, StateSlice>(
api: ReadonlyStoreApi<TState>, // storeAPI
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 함수 정리
- create함수는 인자값으로 , createState 콜백을 전달받음
- createState콜백: 클로저 전역상태에 접근가능한 setter,getter등의 메서드를 전달받고, 이를 활용해 initialState로 사용자 정의 State & Action을 정의할 수 있도록 도와줌
- create함수 호출 반환값은 createStore로 만든 Store에 바인딩된 새로운 useBoundStore훅을 반환
- 해당 hook은 selector만을 전달하여 전역상태를 가져올 수 있음.
전역상태 구조
결론
- zustand에서 만드는 전역상태는 사용자 정의 State와 Action을 기반으로 생성된 캡슐화된 객체이다. StoreAPI는 이러한 캡슐화된 전역상태를 조작할 수 있는 api이다.
- create 방식 사용시
- 전역상태를 생성한다.
- 내부적으로 useSyncExternalStore가 이 전역상태와 React시스템을 연결한다.
- create함수의 반환값인 Hook으로 전역상태를 소비한다.
- createStore를 직접 사용한다면 Provider - Context API 방식으로 사용할 수도 있다.
Introduction
zustand 상태관리 방식을 이해한다는 것은 무엇일까?
zustand 상태관리를 이해하기 위해 아래 네 가지 개념들의 유기적 상관관계를 이해해보도록 한다.
- createStore
- create
- useSyncExternalStore
- useStore
Overview
- 전역상태(zustand): StoreApi ( setState, getState, getInitialState, subscribe )로 접근 가능한 캡슐화된 객체
- 👉 "React 렌더링 사이클과 독립적으로 존재. “
- createStore : 전역상태를 만들고, storeAPI를 반환한다.
- 👉 ”캡슐화된 사용자정의 State 및 Action(메서드)를 만들고, 상태 관리를 위한 핵심 API인 StoreApi ( setState, getState, getInitialState, subscribe )를 반환”
- create : 내부적으로 전역상태를 만들고, 그 전역상태를 구독가능한 Hook까지 반환하는 함수
- 👉 "createStore를 호출하여 스토어를 생성하고, 이를 내부적으로 useStore 훅으로 래핑하여 해당 Store에 Bind된 새로운 useStore Hook 반환”
- useSyncExternalStore : 리액트 시스템 외부에 존재하는 zustand 전역상태와 리액트를 연결하는 기능
- useStore : 전역상태에서 사용자정의 상태의 특정 부분( State / Action )을 select하여 사용
- selector 반환값을 object.is 메서드로 검사하여 리렌더를 결정 (전역상태를 기본적으로 구독)
- create사용시와 달리 대상 target인 Store를 함께 주입해주어야함.
Deep Dive
createStore
JAVASCRIPT
export const createStore = (
(createState) => createState
? createStoreImpl(createState) // 케이스 1: 직접 콜백 전달
: createStoreImpl // 케이스 2: createStoreImpl 함수 반환
) as CreateStore
- 콜백을 넣고 바로 호출할지
- createStore( callbackFn ) : store
- 콜백없이 호출하고 그다음 콜백을 넣어서 호출할지
- createStore()( callbackFn) : store
- 결국 동일한 createStoreImpl(createState) 결과값을 가진다.
createStoreImpl
BASH
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함수 호출이후에 호출되는 구조로 강제되어있음.
createStoreImpl 역할
- 전역상태를 만들고
- 전역상태를 컨트롤 할 수 있는 storeAPI를 외부로 반환
- 이 때, 전역상태는 캡슐화 되어 storeAPI로만 get, set이 가능하다.
createStore 함수 정리
- 캡슐화된 전역상태를 만든다.
- 전역상태에 접근가능한 storeAPI를 반환한다.
create
TYPESCRIPT
export const create = (<T>(createState: StateCreator<T, [], []> | undefined) =>
createState ? createImpl(createState) : createImpl) as Create
- create(createState)
- create()(createState)
// 위 사용법을 위한 단순한 분기처리이다.
createImpl
TYPESCRIPT
const createImpl = <T>(createState: StateCreator<T, [], []>) => {
const api = createStore(createState) // 전역 store생성, storeAPI반환
const useBoundStore: any = (selector?: any) => useStore(api, selector)
Object.assign(useBoundStore, api)
return useBoundStore
}
- useStore훅에 storeAPI를 바인딩하여 useBoundStore형태의 새로운 훅을 반환한다.
useStore
TYPESCRIPT
export function useStore<TState, StateSlice>(
api: ReadonlyStoreApi<TState>, // storeAPI
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 함수 정리
- create함수는 인자값으로 , createState 콜백을 전달받음
- createState콜백: 클로저 전역상태에 접근가능한 setter,getter등의 메서드를 전달받고, 이를 활용해 initialState로 사용자 정의 State & Action을 정의할 수 있도록 도와줌
- create함수 호출 반환값은 createStore로 만든 Store에 바인딩된 새로운 useBoundStore훅을 반환
- 해당 hook은 selector만을 전달하여 전역상태를 가져올 수 있음.
전역상태 구조
결론
- zustand에서 만드는 전역상태는 사용자 정의 State와 Action을 기반으로 생성된 캡슐화된 객체이다. StoreAPI는 이러한 캡슐화된 전역상태를 조작할 수 있는 api이다.
- create 방식 사용시
- 전역상태를 생성한다.
- 내부적으로 useSyncExternalStore가 이 전역상태와 React시스템을 연결한다.
- create함수의 반환값인 Hook으로 전역상태를 소비한다.
- createStore를 직접 사용한다면 Provider - Context API 방식으로 사용할 수도 있다.