Redux 는 장단점만 가볍게 정리하려고 한다.
flux 패턴을 사용함으로 단방향 데이터 흐름을 가져간다. -> 데이터를 예측가능하게 하고 데이터 흐름의 복잡성을 해결한다.
react의 초반 전역 상태 라이브러리의 표준을 잡아주는 역할을 했기 때문에 무시할 수 없는 사용량과 커뮤니티
무게가 무겁다.
복잡한 코드 작성, 기능을 위한 보일러 플레이트가 많아서 러닝커브가 높음
zustand란 전역 상태 관리 라이브러리로 redux에 비해 상대적으로 간단하고 직관적인 문법을 제공한다. Flux패턴을 사용하여 단방향의 데이터 흐름을 가지고 있어, 변화를 추적하기 용이하고 데이터 흐름의 복잡성을 해결한다. pub/sub 모델을 가지고 있어 느슨한 결합으로 인한 확장성에서 장점을 가지고 있다.
zustand의 로직(ts 제외하고 로직만 살펴봄)을 살펴보면 아래와 같은데 state를 클로져를 통해 관리한다. 클로저로 상태를 관리함으로써 가져갈 수 있는 이점은
1. 외부에서의 무분별한 접근과 수정을 막기 때문에 데이터 무결성을 지킬 수 있다.
2. 스코프가 명확하고 순수함수로 이루어져 있기에 의도치 않은 사이드이펙트를 막을 수 있고 예측 가능하게 동작한다.
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
: Object.assign({}, state, nextState);
listeners.forEach(listener => listener(state, previousState));
}
};
const getState = () => state;
const subscribe = listener => {
// ... (생략)
};
const api = { setState, getState, subscribe };
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
: Object.assign({}, state, nextState);
listeners.forEach(listener => listener(state, previousState));
}
};
react 18 버전에서 cuncurrent rendering이 지원되면서 외부 스토어(zustand store, DOM 객체 등...)을 참조할 경우에 tearing 문제가 발생할 가능성이 생겼다.
이러한 tearing 문제를 해결하기 위헤 useSyncExternalStore라는 긴 이름의 React hook 을 사용한다. 외부 스토어에 대한 싱크로나이제이션을 강제하는 훅으로 알고있다. 해당 훅은 18버전에서만 사용할 수 있다.
그렇기 때문에 zustand는 useSyncExternalStore 훅을 사용하여 위와 같은 문제를 해결하고 있다. (코드)
아래는 useStore 함수 안에 있는 useSyncExternalStore의 모습.. 사실 useSyncExternalStore의 사용법을 잘 몰라서 위의 로직과 달리 직관적으로 이해가 안된다ㅠㅠ.
const slice = useSyncExternalStoreWithSelector(
api.subscribe,
api.getState,
api.getServerState || api.getState,
selector,
equalityFn
)
내가 느낀 바로는 flux 패턴과 pub-sub 모델을 가져가며 데이터에 대한 예측 가능성과 무결성, 내부 코드의 불변성도 가져가고 있고 devTools 지원에도 문제가 없고.. redux 보다 가볍고 러닝커브가 낮은 zustand를 더 높게 평가하고 있다. zustand야 더 힘을 내줘.
js의 proxy를 사용하여 전역상태를 관리하는 라이브러리이다. 사용방법이 아주~ 직관적이고 간단해서 아주 간단한 개인 프로젝트에도 사용한 라이브러리이다. ssr을 사용하고 서버 컴포넌트를 사용하면서 클라이언트 단의 상태 관리 라이브러리의 경량화를 선호하는데 간단히 쓰기 너무 좋았던 라이브러리이다. 사용해본 사람들 말로는 이와 같이 observable한 상태 관리 라이브러리를 사용할거면 jotai를 더 추천한다고 하긴 함. 왜인지는 나중에 jotai 분석하면서 알아보려 함. (구조화 잘되어있고 다양한 상황에 대응하기 좋다는 이야기를 하심.)
proxy의 의미는 대리인 라는 뜻인데 말그대로 객체의 기본동작을 대리하여 다른 동작을 할 수 있게 한다는 것이다. 사용방법도 new Proxy(target, handler) 의 방식으로 target은 proxy의 대상객체, handler는 target을 가로채 동작하게 할 함수. Get, set, has 등등이 있당.
변형 감지를 위해 set Handler를 사용하고 이러한 객체 변형의 추적을 위해 version number를 사용한다.
//생성
const p = new Proxy({}, {
set(target, prop, value) {
++version;
target[prop] = value;
},
});
//라이브러리 로직 내의 version 이 있는 것을 확인할 수 있다.
type CreateSnapshot = <T extends object>(
target: T,
version: number,
handlePromise?: HandlePromise
) => T
snapshot? 변경 불가능한 상태. 스냅샷의 기본은 개체를 복사하는것.
const snapshot = () => {
if (lastVersion !== version) {
lastVersion = version;
lastSnapshot = { ...p }; //여기서 p는 위의 코드 내 p 값
}
return lastSnapshot;
};