ilokesto

Svelte

Svelte store는 subscribe 메서드를 가진 객체입니다. subscriber는 현재 값과 이후 업데이트를 받고, subscribe는 구독 해제 함수를 반환합니다. @ilokesto/store는 작은 wrapper로 이 contract에 맞출 수 있습니다.

readable adapter 만들기

import type { Readable } from "svelte/store";
import { Store } from "@ilokesto/store";

export function toReadable<T>(store: Store<T>): Readable<Readonly<T>> {
  return {
    subscribe(run) {
      run(store.getState());

      return store.subscribe(() => {
        run(store.getState());
      });
    },
  };
}

처음의 run(store.getState())가 중요합니다. Svelte subscriber는 구독하는 순간 현재 값을 받는다고 기대합니다. 이후 store 업데이트가 발생하면 다시 run()을 호출합니다.

필요할 때 write helper 추가하기

import type { Readable } from "svelte/store";
import { Store } from "@ilokesto/store";

type StoreWritable<T> = Readable<Readonly<T>> & {
  set(value: T): void;
  update(updater: (prev: T) => T): void;
};

export function toWritable<T>(store: Store<T>): StoreWritable<T> {
  const readable = toReadable(store);

  return {
    subscribe: readable.subscribe,
    set(value) {
      store.setState(value);
    },
    update(updater) {
      store.setState(updater);
    },
  };
}

컴포넌트가 상태를 관찰만 해야 한다면 readable adapter를 사용하세요. Svelte 컴포넌트가 store를 직접 업데이트해도 되는 경우에만 writable adapter를 사용합니다.

컴포넌트에서 사용하기

<script lang="ts">
  import { Store } from "@ilokesto/store";
  import { toWritable } from "./svelte-store";

  type CounterState = { count: number };

  const counterStore = new Store<CounterState>({ count: 0 });
  const counter = toWritable(counterStore);

  function increment() {
    counter.update((prev) => ({ count: prev.count + 1 }));
  }
</script>

<button type="button" on:click={increment}>
  Count: {$counter.count}
</button>

Svelte는 컴포넌트가 살아 있는 동안 $counter를 자동으로 구독하고, 컴포넌트 cleanup 때 구독을 해제합니다.

소유권을 분명히 하기

다른 모듈도 같은 Store<T>를 업데이트한다면 모든 곳에 set을 노출하기보다 이름 있는 action 함수를 export하는 편이 좋습니다. 그래야 교체 semantics와 미들웨어 기대값을 한곳에 둘 수 있습니다.

목차