Svelte / SvelteKitMiddleCoding

Что такое stores в Svelte? Объясните writable, readable и derived stores.

writable — store с методами set и update, readable — store только для чтения (внешние источники данных), derived — вычисляется из других stores. Все три реализуют контракт { subscribe } и совместимы с авто-подпиской через $.

Stores в Svelte

Store — любой объект с методом subscribe(callback), который вызывает callback немедленно с текущим значением и при каждом его изменении, а также возвращает функцию отписки. Svelte предоставляет три готовых примитива в пакете svelte/store.

writable — записываемый store

import { writable } from 'svelte/store';

const count = writable(0); // начальное значение

// set — установить новое значение
count.set(5);

// update — изменить на основе текущего
count.update(n => n + 1);

// subscribe — подписаться на изменения
const unsubscribe = count.subscribe(value => {
  console.log('count:', value); // вызовется сразу с 0, потом при каждом изменении
});

// Отписаться
unsubscribe();

readable — store только для чтения

Используется для данных из внешних источников: таймеры, WebSocket, геолокация. Второй аргумент — функция start, вызываемая при первом подписчике; возвращает stop для очистки.

import { readable } from 'svelte/store';

// Store текущего времени, обновляется каждую секунду
const time = readable(new Date(), function start(set) {
  const interval = setInterval(() => set(new Date()), 1000);

  // Вызывается при последнем отписчике
  return function stop() {
    clearInterval(interval);
  };
});

// Использование в компоненте:
// <p>{$time.toLocaleTimeString()}</p>

derived — производный store

Вычисляется из одного или нескольких stores. Обновляется автоматически при изменении исходников.

import { writable, derived } from 'svelte/store';

const price = writable(100);
const quantity = writable(3);

// Из одного store
const doubled = derived(price, $price => $price * 2);

// Из нескольких stores
const total = derived(
  [price, quantity],
  ([$price, $quantity]) => $price * $quantity
);

// Асинхронный derived — третий аргумент: начальное значение
const asyncData = derived(
  url,
  ($url, set) => {
    fetch($url)
      .then(r => r.json())
      .then(set);
    return () => {}; // cleanup
  },
  null // начальное значение до первого resolve
);

Кастомный store

Можно создать store с инкапсулированной логикой, экспортируя только нужные методы:

import { writable } from 'svelte/store';

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

  return {
    subscribe, // обязателен для $ авто-подписки
    addItem: (item) => update(items => [...items, item]),
    removeItem: (id) => update(items => items.filter(i => i.id !== id)),
    clear: () => set([])
  };
}

export const cart = createCart();

// В компоненте:
// import { cart } from './stores.js';
// cart.addItem({ id: 1, name: 'Товар' });
// {$cart.length} товаров

Подводные камни

  • readable не запускает start до первой подписки — если никто не подписан, данные не обновляются. Это экономит ресурсы, но может удивить при первом use.
  • derived пересчитывается синхронно для каждого обновления зависимости — тяжёлые вычисления в derived могут тормозить UI; используйте debounce внутри.
  • Подписка в обычном JS возвращает unsubscribe — забытый вызов отписки — утечка памяти. Только в .svelte файлах $ управляет этим автоматически.
  • Начальное значение derived по умолчанию undefined — если используется в шаблоне без fallback, может рендерить пустоту до первого вычисления.
  • Циклические зависимости между derived-stores приводят к бесконечному циклу обновлений.
  • update() не атомарен при множественных вызовах подряд — если нужно изменить несколько stores одновременно, используйте svelte/store batch (или рефакторите в один store).
  • В Svelte 5 stores остаются для обратной совместимости, но для нового кода рекомендуются руны $state и $derived.

Common mistakes

  • Путать Svelte stores с похожим API из соседнего фреймворка.
  • Не объяснять, где код выполняется: сервер, клиент, build step или runtime.
  • Игнорировать влияние на hydration, cache, bundle size или безопасность.

What the interviewer is testing

  • Точно объясняет назначение механизма «Svelte stores».
  • Показывает корректный минимальный пример без выдуманных API.
  • Называет ограничения, failure modes и production-компромиссы.

Sources

Related topics