일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- TypeScript
- STATE
- React
- 프로젝트캠프
- 프론트엔드 개발
- 스나이퍼팩토리
- Server State
- frontend
- 웅진씽크빅
- 수코딩
- sucoding
- React Query
- 프론트엔드
- tanstack query
- 공식문서
- 리액트프로젝트
- 개발
- 유데미
- 상태 관리 라이브러리
- Today
- Total
yunicornlab
Zustand의 create 함수 알아보기 (1) - set 함수 본문
React에서 Zustand를 사용해 store를 생성하려면 create 함수를 사용한다.
create 함수에는 콜백 함수를 인자로 전달하는데, 이 콜백 함수에는 set과 get 함수를 인자로 받아서 사용할 수 있다고 했다.
단순히 공부할 때는 get은 상태값을 받아올 수 있는 함수이고 set은 상태를 변경할 수 있는 함수라는 것만 알고있었는데,
이것밖에 없는 건지, create 함수는 어떻게 생긴건지 무엇일까 궁금해져서 공부해보았다.
먼저, zustand github 레포를 찾아서 이 set 함수 부분이 있는 곳을 찾아보았다.
https://github.com/pmndrs/zustand/blob/a958de910fb49392d5407eb0a9a776ec959ce8c5/src/vanilla.ts
zustand/src/vanilla.ts at a958de910fb49392d5407eb0a9a776ec959ce8c5 · pmndrs/zustand
🐻 Bear necessities for state management in React. Contribute to pmndrs/zustand development by creating an account on GitHub.
github.com
A small, fast, and scalable bearbones state management solution. Zustand has a comfy API based on hooks.
위치는 src > vanilla.ts에 있었고 이 중 createSotreImpl 안의 setState로 정의된 함수가 계속 말해왔던 set 함수였다.
타입 정의 부분은 빼고 핵심 부분만 가져와보면 아래와 같다.
const createStoreImpl: CreateStoreImpl = (createState) => {
const listeners: Set<Listener> = new Set()
const setState: StoreApi<TState>['setState'] = (partial, replace) => {
// TODO: Remove type assertion once https://github.com/microsoft/TypeScript/issues/37663 is resolved
// https://github.com/microsoft/TypeScript/issues/37663#issuecomment-759728342
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))
}
};
const getState: StoreApi<TState>['getState'] = () => state
const getInitialState: StoreApi<TState>['getInitialState'] = () =>
initialState
const subscribe: StoreApi<TState>['subscribe'] = (listener) => {
listeners.add(listener)
// Unsubscribe
return () => listeners.delete(listener)
}
const api = { setState, getState, getInitialState, subscribe }
const initialState = (state = createState(setState, getState, api))
return api as any
}
export const createStore = ((createState) =>
createState ? createStoreImpl(createState) : createStoreImpl) as CreateStore
createStoreImpl에서 상태 관리 관련 API 객체인 api 변수를 return하기 때문에
(이 코드의 마지막 부분인) createStore에는 { setState, getState, getInitialState, subscribe }가 담기게 된다.
React에서 사용할 코드는 src > react.ts에 있었다. 마찬가지로 타입 정의 부분은 빼고 핵심 부분만 가져오면 아래와 같다.
여기에 useStore와 create가 정의되어 있다.
해당 코드는 다음 글에서 다시 얘기해보려 한다.
import React from 'react'
import { createStore } from './vanilla.ts'
export function useStore<TState, StateSlice>(
api: ReadonlyStoreApi<TState>,
selector: (state: TState) => StateSlice = identity as any,
) {
const slice = React.useSyncExternalStore(
api.subscribe,
() => selector(api.getState()),
() => selector(api.getInitialState()),
)
React.useDebugValue(slice)
return slice
}
const createImpl = <T>(createState: StateCreator<T, [], []>) => {
const api = createStore(createState)
const useBoundStore: any = (selector?: any) => useStore(api, selector)
Object.assign(useBoundStore, api)
return useBoundStore
}
export const create = (<T>(createState: StateCreator<T, [], []> | undefined) =>
createState ? createImpl(createState) : createImpl) as Create
set 함수
createSotre 관련 코드 중 setState 함수 부분만 빼서 자바스크립트로 바꿔보면 아래와 같다.
const setState = (partial, replace) => {
// nextState 계산: partial이 함수인지 확인
const nextState =
typeof partial === 'function'
? partial(state) // partial이 함수라면 state를 인자로 호출
: partial; // partial이 객체라면 그대로 사용
// nextState가 현재 state와 다르면 상태를 업데이트
if (!Object.is(nextState, state)) {
const previousState = state; // 이전 상태 저장
// replace가 true이거나 nextState가 객체가 아니면 nextState로 교체
state =
(replace || typeof nextState !== 'object' || nextState === null)
? nextState
: Object.assign({}, state, nextState); // 그렇지 않으면 기존 상태와 병합
// 상태 변경 후 listeners 호출
listeners.forEach((listener) => listener(state, previousState));
}
};
즉, set 함수는 partial라는 상태를 업데이트하는 함수와 replace, 이렇게 두 개의 인자를 받는다.
첫 번째 인자 자리(partial)에 상태를 변경하는 로직을 담은 함수를 넣어주면 되고,
두 번째 인자 자리(replace)는 기본값이 false인데 이는 상태를 병합한다는 의미다.
만약 상태를 병합하는 것이 아니라 완전히 새로운 상태로 교체하고 싶다면 두 번째 인자 자리(replace)에 true를 넣어주면 된다.
상황을 나눠보면 다음과 같은 네 가지 상태가 올 수 있다. (실제로는 1번과 3번을 많이 쓰게 될 것 같다.)
1. partial 자리에 객체가 오는 경우 + replace가 기본값인 false인 경우 : 병합됨
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
text: 'hello',
// 객체로 상태 변경
setCount: () => set({ count: 10 }),
}));
export default useStore;
- 기존 상태: { count: 0, text: 'hello' }
- 새 상태: { count: 10 }
- 결과 상태: { count: 10, text: 'hello' } (병합)
2. partial 자리에 객체가 오는 경우 + replace를 true로 둔 경우 : 완전히 새로 교체됨
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
text: 'hello',
// 객체로 상태 변경 + 기존 상태를 완전히 교체
setCount: () => set({ count: 10 }, true),
}));
export default useStore;
(권장하지는 않는다.)
- 기존 상태: { count: 0, text: 'hello' }
- 새 상태: { count: 10 }
- 결과 상태: { count: 10 } (교체)
3. partial 자리에 함수가 오는 경우 +replace는 기본값인 false인 경우
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
text: 'hello',
// 함수로 상태 변경 및 병합
setCount: () =>
set((state) => ({ count: state.count + 1 }), false),
}));
- 기존 상태: { count: 0, text: 'hello' }
- 새 상태 함수: (state) => ({ count: state.count + 1 })
- 결과 상태: { count: 1, text: 'hello' } (병합)
4. partial 자리에 함수가 오는 경우 +replace는 기본값인 true인 경우
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
text: 'hello',
// 함수로 상태 변경 및 교체
setCount: () =>
set((state) => ({ count: state.count + 1 }), true),
}));
- 기존 상태: { count: 0, text: 'hello' }
- 새 상태 함수: (state) => ({ count: state.count + 1 })
- 결과 상태: { count: 1 } (교체)
console에 출력해보기
실제 console로 찍어보면 어떻게 나올지 궁금해서 해봤다.
useStore.js를 아래처럼 작성하면
import { create } from 'zustand';
console.log('create')
console.log(create())
const useStore = create((setState, getState, getInitialState) => {
console.log(`set :\n\n${setState}`)
console.log(`get :\n\n${getState}`)
console.log(`init :\n\n${getInitialState}`)
return {
count: 0, // 상태 1
text: '', // 상태 2
increaseCount: () => set((state) => ({ count: state.count + 1 })), // count 증가
updateText: (newText) => set({ text: newText }), // text 업데이트
}});
export default useStore;
이렇게 나온다.
실제 사용할 때 쓰는 create와 useStoresms 위에서 잠깐 언급했던 react.ts 파일에 정의되어 있는데,
이 부분은 다음 글에 적어보려 한다!!🥹
'Frontend > State' 카테고리의 다른 글
Zustand에서 Selector 사용 방법에 따른 차이 알아보기 (0) | 2024.12.12 |
---|---|
Zustand의 create 함수 알아보기 (2) - create와 useStore (0) | 2024.12.12 |
zustand가 뭐야 어떻게 써 (0) | 2024.12.11 |