유틸리티 소개
@ilokesto/state/utils는 framework adapter에 묶이지 않는 pipe helper를 export합니다. 별도 @ilokesto/state/adaptor subpath는 Immer 기반 update helper를 export합니다.
pipe는@ilokesto/storeinstance를 만들고 등록된 middleware를 순서대로 적용합니다.definePipeableMiddleware는pipe.use(...)에 쓸 custom middleware metadata를 등록합니다.PipeConfigurationError는 잘못된 runtime pipe configuration을 보고합니다.adaptor는 Immerproduce를 감싸 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 | 사용할 때 | 피할 때 |
|---|---|---|
pipe | store를 만들고 등록된 middleware를 왼쪽에서 오른쪽으로 적용하고 싶을 때 | 이미 소유한 Store<T> instance를 직접 조작해야 할 때 |
definePipeableMiddleware | pipe chain에 custom middleware를 노출할 때 | middleware를 pipe 밖에서만 사용할 때 |
adaptor | object update가 draft mutation으로 더 읽기 쉬울 때 | state가 primitive이거나 immer를 설치하고 싶지 않을 때 |
일반적인 composition flow
- initial state를 정의합니다.
- persistence, logging, validation, debouncing, DevTools가 필요하면
pipe로 middleware를 조합합니다. - 결과 store를 framework adapter의
create에 넘깁니다. - 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이 더 적합한 경우를 설명합니다.