오늘은 zustand가 내부적으로 어떻게 동작하는 알아보기 위해 zustand github를 확인해 봤습니다.
파일이 타입스크립트로 작성되어 있는데 보기 편하기 위해 GPT의 힘을 빌려 자바스크립트로 변경했습니다.
react.ts 📖
`react.ts`파일의 전체 코드이고, React와의 연동을 해줍니다.
import ReactExports from 'react';
import { createStore } from './vanilla.js';
const { useDebugValue, useSyncExternalStore } = ReactExports;
const identity = (arg) => arg;
export function useStore(api, selector = identity) {
const slice = useSyncExternalStore(
api.subscribe,
() => selector(api.getState()),
() => selector(api.getInitialState()),
);
useDebugValue(slice);
return slice;
}
const createImpl = (createState) => {
const api = createStore(createState);
const useBoundStore = (selector) => useStore(api, selector);
Object.assign(useBoundStore, api);
return useBoundStore;
};
export const create = (createState) =>
createState ? createImpl(createState) : createImpl;
React에서 사용할 때
const useCountStore = create((set) => ({
}));
create함수를 사용하면 `createStore`를 사용해 store를 만들어주고, `useBoundStore`함수를 통해 selector를 받을 수 있는 훅으로 감싸주게 됩니다.
// 구현체
const createImpl = (createState) => {
const api = createStore(createState); // createStore 사용해 store 만들기
const useBoundStore = (selector) => useStore(api, selector);
Object.assign(useBoundStore, api);
return useBoundStore;
};
// 추상체
export const create = (createState) =>
createState ? createImpl(createState) : createImpl;
사용할 때 이렇게 두 가지 방법으로 사용할 수 있게끔 해줍니다.
const { count, increase, decrease } = useCountStore(); // 1
const count = useCountStore(state => state.count); // 2
React 컴포넌트는 `react.ts`파일의 `useStore`함수를 사용해 zustand의 상태변화를 감지하고 `useSyncExternalStore`훅을 사용해 외부 상태를 구독하고, 상태 변경 시 컴포넌트를 업데이트합니다.
import ReactExports from 'react';
import { createStore } from './vanilla.js';
const { useDebugValue, useSyncExternalStore } = ReactExports;
const identity = (arg) => arg;
export function useStore(api, selector = identity) {
const slice = useSyncExternalStore(
api.subscribe,
() => selector(api.getState()),
() => selector(api.getInitialState()),
);
useDebugValue(slice);
return slice;
}
`useStore`함수는 `useSyncExternalStore`를 활용해 React 컴포넌트가 zustand의 상태 변화를 감지하고, 변화가 생길 때마다 컴포넌트를 리렌더링 합니다.
✨간단하게 정리하면
`createStore`를 이용해 `store(상태 관리 객체)`를 만들어 `useStore`함수 인자로 전달하고 `useSyncExternalStore`를 활용해 외부 상태(zustand) 변화를 감지해 리렌더링 한다.
❗useSyncExternalStore 훅이 궁금하다면
vanilla.ts 📖
`vanilla.ts`파일은 상태를 생성하고 변경할 수 있는 기본적인 로직을 제공하고, 상태를 관리하는 `setState``getState``getInitialState``subscribe` 4가지 클로저를 정의합니다.
`setState` 함수는 상태를 변경하며, 상태가 변경되면 `listeners`에 등록된 모든 리스너 함수를 실행해 상태 변화를 알립니다.
const createStoreImpl = (createState) => {
let state;
const listeners = new Set();
const setState = (partial, replace) => {
const nextState = typeof partial === 'function' ? partial(state) : partial;
if (!Object.is(nextState, state)) {
const previousState = state;
state = replace ?? (typeof nextState !== 'object' || nextState === null)
? nextState
: { ...state, ...nextState };
listeners.forEach((listener) => listener(state, previousState));
}
};
const getState = () => state;
const getInitialState = () => initialState;
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const api = { setState, getState, getInitialState, subscribe };
const initialState = (state = createState(setState, getState, api));
return api;
};
setState 동작 방식
const setState = (partial, replace) => {
const nextState = typeof partial === 'function' ? partial(state) : partial;
if (!Object.is(nextState, state)) {
const previousState = state;
state = replace ?? (typeof nextState !== 'object' || nextState === null)
? nextState
: { ...state, ...nextState };
listeners.forEach((listener) => listener(state, previousState));
}
};
1. 인자
const setState = (partial, replace) => {}
`partial`은 상태를 변경하기 위한 값 또는 함수를 받습니다.
`replace`가 `true`인 경우, 기존 상태를 교체하고 `false`또는 `undefined`인 경우, 기존 상태를 유지하면서 변경된 부분만 덮어씁니다.
2. 상태 비교
if (!Object.is(nextState, state))
새로운 상태와 기존 상태를 비교해 동일한지 확인하고 동일하다면 상태 변경을 수행하지 않고 종료됩니다.
3. 상태 업데이트
replace ?? (typeof nextState !== 'object' || nextState === null)
? nextState
: { ...state, ...nextState };
replace가 `true`이거나 `nextState`가 객체가 아닌 경우, 기존 상태를 교체하고 `state`는 `nextState`로 바뀝니다.
만약 `replace`가 `false`이거나 `undefined`이고 `nextState`가 객체라면 Spread 연산자를 사용해 기존 상태와 변경된 `nextState`를 합칩니다.
4. 리스너 실행
listeners.forEach((listener) => listener(state, previousState));
상태가 변경되면 `listeners`에 등록된 모든 리스너가 실행됩니다. 리스너는 상태가 변경될 때마다 호출되며, 컴포넌트 상태 변화를 감지해 리렌더링 합니다.
setState 사용
// 객체를 사용하여 상태 업데이트
setState({ count: 1 });
// 함수로 상태 업데이트
setState((state) => ({ count: state.count + 1 }));
// 상태를 교체하기
setState({ count: 3 }, true);
출처 🏷️
'공부 > 상태 관리' 카테고리의 다른 글
전역 상태 관리, 정말 필요한가? (0) | 2024.12.10 |
---|---|
Zustand 알아보기 (0) | 2024.09.06 |
TanStack Query 알아보기 3 (Query Cancellation, Optimistic Updates, Prefetching, Paginated, Infinite Queries) (0) | 2024.09.06 |
TanStack Query 알아보기 2 (동작 원리) (0) | 2024.09.06 |
TanStack Query 알아보기 1 (기본 사용법) (0) | 2024.09.06 |