Store를 그냥 쓰는 건 쉽습니다. 진짜 실력은 "잘 설계된 커스텀 Store"를 만드는 데서 나옵니다.

개념 정의

커스텀 Store 는 writable을 기반으로 도메인 로직을 캡슐화한 상태 관리 모듈입니다. subscribe 메서드를 가진 객체면 무엇이든 Svelte Store로 사용할 수 있습니다.

Store 프로토콜

Svelte에서 $ 자동 구독이 동작하려면 subscribe 메서드만 있으면 됩니다.

JAVASCRIPT
// 최소 Store 구현
function createMinimalStore(initial) {
  let value = initial;
  const subscribers = new Set();

  return {
    subscribe(fn) {
      subscribers.add(fn);
      fn(value); // 즉시 현재 값 전달
      return () => subscribers.delete(fn);
    },
    set(newValue) {
      value = newValue;
      subscribers.forEach(fn => fn(value));
    }
  };
}

실전 패턴 — Todo Store

JAVASCRIPT
// stores/todoStore.js
import { writable, derived } from 'svelte/store';

function createTodoStore() {
  const { subscribe, update, set } = writable([]);

  return {
    subscribe,

    add(text) {
      update(todos => [...todos, {
        id: crypto.randomUUID(),
        text,
        done: false,
        createdAt: new Date(),
      }]);
    },

    remove(id) {
      update(todos => todos.filter(t => t.id !== id));
    },

    toggle(id) {
      update(todos => todos.map(t =>
        t.id === id ? { ...t, done: !t.done } : t
      ));
    },

    clear() {
      set([]);
    },

    clearCompleted() {
      update(todos => todos.filter(t => !t.done));
    },
  };
}

export const todos = createTodoStore();

// 파생 스토어
export const activeTodos = derived(todos, $todos =>
  $todos.filter(t => !t.done)
);
export const completedCount = derived(todos, $todos =>
  $todos.filter(t => t.done).length
);

비동기 Store

JAVASCRIPT
// stores/apiStore.js
import { writable } from 'svelte/store';

function createAsyncStore(fetchFn) {
  const { subscribe, set, update } = writable({
    data: null,
    loading: false,
    error: null,
  });

  return {
    subscribe,

    async fetch(...args) {
      update(s => ({ ...s, loading: true, error: null }));
      try {
        const data = await fetchFn(...args);
        set({ data, loading: false, error: null });
        return data;
      } catch (error) {
        update(s => ({ ...s, loading: false, error: error.message }));
        throw error;
      }
    },

    reset() {
      set({ data: null, loading: false, error: null });
    },
  };
}

// 사용
export const usersStore = createAsyncStore(
  () => fetch('/api/users').then(r => r.json())
);

localStorage 동기화 Store

JAVASCRIPT
// stores/persistentStore.js
import { writable } from 'svelte/store';
import { browser } from '$app/environment';

function persistent(key, initialValue) {
  const stored = browser ? localStorage.getItem(key) : null;
  const initial = stored ? JSON.parse(stored) : initialValue;

  const store = writable(initial);

  if (browser) {
    store.subscribe(value => {
      localStorage.setItem(key, JSON.stringify(value));
    });
  }

  return store;
}

export const theme = persistent('theme', 'light');
export const language = persistent('language', 'ko');

Undo/Redo Store

JAVASCRIPT
function createUndoStore(initialValue) {
  const { subscribe, set } = writable(initialValue);
  let history = [initialValue];
  let index = 0;

  return {
    subscribe,

    push(value) {
      history = history.slice(0, index + 1);
      history.push(value);
      index++;
      set(value);
    },

    undo() {
      if (index > 0) {
        index--;
        set(history[index]);
      }
    },

    redo() {
      if (index < history.length - 1) {
        index++;
        set(history[index]);
      }
    },

    get canUndo() { return index > 0; },
    get canRedo() { return index < history.length - 1; },
  };
}

면접 포인트

  • "Store Contract란?": subscribe 메서드를 가진 객체면 Svelte Store로 동작합니다. RxJS Observable도 subscribe가 있으므로 $ 접두사로 자동 구독이 가능합니다.
  • "커스텀 Store의 이점은?": 상태 변경 로직을 캡슐화하여 컴포넌트에서 직접 상태를 조작하는 것을 방지합니다. 비즈니스 로직의 단일 책임 원칙을 적용할 수 있습니다.

정리

커스텀 Store는 "상태 + 로직"을 하나의 모듈로 묶는 패턴입니다. writable을 감싸서 도메인별 메서드를 노출하면, 컴포넌트는 "무엇을 할지"만 선택하고 "어떻게 할지"는 Store가 책임집니다.

댓글 로딩 중...