ilokesto

유틸리티 소개

@ilokesto/state/utils는 framework adapter에 묶이지 않는 pipe helper를 export합니다. 별도 @ilokesto/state/adaptor subpath는 Immer 기반 update helper를 export합니다.

  • pipe는 @ilokesto/store instance를 만들고 등록된 middleware를 순서대로 적용합니다.
  • definePipeableMiddleware는 pipe.use(...)에 쓸 custom middleware metadata를 등록합니다.
  • PipeConfigurationError는 잘못된 runtime pipe configuration을 보고합니다.
  • adaptor는 Immer produce를 감싸 object update를 draft mutation syntax로 쓸 수 있게 합니다.

React, Vue, Svelte, Solid, Angular에 연결하기 전에 store 자체를 준비할 때 Utility page를 보세요.

왜 adapter와 utility가 분리되어 있나

Framework adapter는 “이 state를 UI에서 어떻게 reactive하게 읽을까?”에 답합니다. Utility는 “state 자체를 어떻게 준비하거나 작성할까?”에 답합니다. 이 분리 덕분에 같은 store setup을 여러 framework나 UI 밖 코드에서 재사용할 수 있습니다.

import { create } from '@ilokesto/state/react';
import { jsonStorage, logger, persist } from '@ilokesto/state/middleware';
import { pipe } from '@ilokesto/state/utils';

type PreferencesState = { theme: 'system' | 'light' | 'dark' };

const decodePreferences = (value: unknown): PreferencesState | null => {
  if (typeof value !== 'object' || value === null || !('theme' in value)) return null;
  return value.theme === 'system' || value.theme === 'light' || value.theme === 'dark'
    ? { theme: value.theme }
    : null;
};

const preferencesStore = pipe
  .use(persist({ key: 'preferences', storage: jsonStorage(() => window.localStorage), decode: decodePreferences }))
  .use(logger({ collapsed: true }))
  .create<PreferencesState>({ theme: 'system' });

export const usePreferences = create(preferencesStore);

export async function restorePreferences() {
  await preferencesStore.persist.rehydrate();
}

Component는 여전히 일반 adapter API를 사용합니다. 클라이언트 시작 코드나 effect에서 restorePreferences()를 호출하고 오류를 처리한 뒤 편집을 활성화하세요. Utility layer는 create에 넘길 store를 준비할 뿐이며 생성 시 저장소 I/O는 없습니다.

설치 메모

pipe는 @ilokesto/state와 그 dependency인 @ilokesto/store만 있으면 됩니다.

adaptor는 optional peer dependency인 immer를 사용합니다. adaptor를 사용할 때만 설치하세요.

pnpm add immer

어떤 utility를 고를까

Utility사용할 때피할 때
pipestore를 만들고 등록된 middleware를 왼쪽에서 오른쪽으로 적용하고 싶을 때이미 소유한 Store<T> instance를 직접 조작해야 할 때
definePipeableMiddlewarepipe chain에 custom middleware를 노출할 때middleware를 pipe 밖에서만 사용할 때
adaptorobject update가 draft mutation으로 더 읽기 쉬울 때state가 primitive이거나 immer를 설치하고 싶지 않을 때

일반적인 composition flow

  1. initial state를 정의합니다.
  2. persistence, logging, validation, debouncing, DevTools가 필요하면 pipe로 middleware를 조합합니다.
  3. 결과 store를 framework adapter의 create에 넘깁니다.
  4. Draft syntax가 가독성을 높이는 곳에서만 setState 안에 adaptor를 사용합니다.
import { create } from '@ilokesto/state/react';
import { validate } from '@ilokesto/state/middleware';
import { adaptor } from '@ilokesto/state/adaptor';
import { pipe } from '@ilokesto/state/utils';

const profileStore = pipe
  .use(validate(profileSchema))
  .create({ name: '', tags: [] as string[] });

const useProfile = create(profileStore);

function AddTagButton({ tag }: { tag: string }) {
  const [, setProfile] = useProfile((state) => state.tags);

  return (
    <button
      onClick={() =>
        setProfile(adaptor((draft) => {
          draft.tags.push(tag);
        }))
      }
    >
      Add tag
    </button>
  );
}

관련 문서

  • Middleware는 pipe와 함께 자주 조합하는 middleware function을 설명합니다.
  • Plain state 가이드는 setState 스타일 update가 충분한 경우를 설명합니다.
  • Reducer state 가이드는 이름 있는 action이 더 적합한 경우를 설명합니다.

목차