ilokesto

핵심 개념

@ilokesto/store의 모델은 의도적으로 작습니다. Store<T>가 상태 값 하나를 소유하고, setState()가 다음 값을 계산하며, Object.is가 변경 여부를 판단하고, 성공적으로 교체된 뒤 리스너가 실행됩니다.

하나의 store는 하나의 값을 소유합니다

const authStore = new Store({ userId: null as string | null });

타입 파라미터는 저장되는 전체 값을 설명합니다. primitive, 배열, 객체, 더 풍부한 도메인 상태를 저장할 수 있지만 store는 그 값을 하나의 단위로만 압니다.

읽기는 동기 snapshot입니다

getState()는 현재 상태를 즉시 반환합니다. 호출자를 구독시키거나, 값을 복제하거나, 비동기 작업을 기다리지 않습니다. UI를 계속 동기화해야 한다면 프레임워크 어댑터에서 getState()subscribe()를 함께 사용하세요.

getInitialState()는 constructor에 전달한 값을 반환합니다. 초기 값이 객체였고 나중에 store 밖에서 그 객체를 직접 수정하면, store는 그런 mutation을 막아주지 못합니다.

업데이트는 병합이 아니라 교체입니다

const profileStore = new Store({ name: "Ada", theme: "dark" });

profileStore.setState({ name: "Ada" });
// 다음 상태는 { name: "Ada" }입니다. 기존 theme 필드는 사라집니다.

이 동작은 의도적입니다. 교체 방식은 숨겨진 shallow merge나 deep merge 규칙 없이 예측 가능성을 유지합니다. 필드를 보존하려면 직접 복사하세요.

profileStore.setState((prev) => ({ ...prev, theme: "light" }));

동등성은 알림을 제어합니다

미들웨어가 끝나면 store는 다음 값을 계산합니다. Object.is(prevState, resolvedState)가 true이면 store는 바로 반환하고 리스너를 호출하지 않습니다.

따라서 객체를 제자리에서 직접 수정한 뒤 같은 참조를 반환하면 버그입니다.

profileStore.setState((prev) => {
  prev.theme = "light";
  return prev; // 같은 참조라서 구독자에게 알리지 않습니다
});

대신 새 객체나 새 배열을 반환하세요.

구독자는 명시적입니다

subscribe(listener)는 리스너를 추가하고 구독 해제 함수를 반환합니다. 리스너는 인자를 받지 않습니다. 리스너 안에서 getState()로 상태를 읽으세요.

const unsubscribe = profileStore.subscribe(() => {
  render(profileStore.getState());
});

unsubscribe();

cleanup 함수는 반드시 리스너를 소유한 lifecycle에 연결하세요.

미들웨어는 업데이트를 감쌉니다

pushMiddleware()는 미들웨어를 배열 끝에 추가합니다. unshiftMiddleware()는 미들웨어를 앞에 넣습니다. setState() 동안 store는 reduceRight로 오른쪽에서 왼쪽으로 chain을 만들기 때문에 배열에서 앞에 있는 미들웨어가 업데이트를 먼저 받습니다.

미들웨어는 로그, 검증, 변환, 차단, 전달을 할 수 있습니다. 이후 미들웨어와 최종 상태 적용이 실행되려면 반드시 next(...)를 호출해야 합니다.

목차