Настройка State Management (Svelte Store) для Svelte-приложения

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка State Management (Svelte Store) для Svelte-приложения
Простая
от 4 часов до 2 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Настройка State Management (Svelte Store) для Svelte-приложения

Svelte имеет встроенную систему сторов — никаких внешних библиотек не нужно. Реактивность строится на writable, readable и derived сторах из svelte/store. Компонент подписывается на стор через префикс $ — компилятор Svelte генерирует подписку и отписку автоматически.

Для большинства Svelte-приложений встроенных сторов достаточно. Сложные случаи — cross-store derived, async-данные с состоянием загрузки, персистентность — решаются без сторонних зависимостей.

Что входит в работу

Проектирование архитектуры сторов под проект, writable/readable/derived, кастомные сторы с инкапсулированной логикой, async-данные, персистентность, типизация TypeScript, тестирование.

Базовые сторы

// stores/cart.ts
import { writable, derived, get } from 'svelte/store'

export interface CartItem {
  id: string
  name: string
  price: number
  quantity: number
}

function createCartStore() {
  const { subscribe, set, update } = writable<CartItem[]>([])

  return {
    subscribe,

    addItem(product: Omit<CartItem, 'quantity'>) {
      update((items) => {
        const existing = items.find((i) => i.id === product.id)
        if (existing) {
          return items.map((i) =>
            i.id === product.id ? { ...i, quantity: i.quantity + 1 } : i
          )
        }
        return [...items, { ...product, quantity: 1 }]
      })
    },

    removeItem(id: string) {
      update((items) => items.filter((i) => i.id !== id))
    },

    updateQuantity(id: string, quantity: number) {
      if (quantity <= 0) {
        update((items) => items.filter((i) => i.id !== id))
        return
      }
      update((items) =>
        items.map((i) => (i.id === id ? { ...i, quantity } : i))
      )
    },

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

export const cart = createCartStore()

// derived — производные значения
export const cartTotal = derived(cart, ($items) =>
  $items.reduce((sum, i) => sum + i.price * i.quantity, 0)
)

export const cartCount = derived(cart, ($items) =>
  $items.reduce((sum, i) => sum + i.quantity, 0)
)

export const isEmpty = derived(cart, ($items) => $items.length === 0)

Использование в компоненте

<script lang="ts">
  import { cart, cartTotal, cartCount, isEmpty } from '$lib/stores/cart'

  function handleAddToCart(product: Product) {
    cart.addItem(product)
  }
</script>

<button on:click={() => handleAddToCart(product)}>
  В корзину
</button>

{#if !$isEmpty}
  <div class="cart-summary">
    <span>Товаров: {$cartCount}</span>
    <span>Итого: {$cartTotal} ₽</span>
    <button on:click={() => cart.clear()}>Очистить</button>
  </div>
{/if}

Префикс $ перед именем стора — авто-подписка. Компилятор Svelte трансформирует это в cart.subscribe(...) с отпиской при destroy компонента.

Auth-стор

// stores/auth.ts
import { writable, derived } from 'svelte/store'

interface AuthState {
  user: User | null
  token: string | null
  loading: boolean
  error: string | null
}

function createAuthStore() {
  const { subscribe, set, update } = writable<AuthState>({
    user: null,
    token: localStorage.getItem('token'),
    loading: false,
    error: null,
  })

  return {
    subscribe,

    async login(credentials: LoginCredentials) {
      update((s) => ({ ...s, loading: true, error: null }))
      try {
        const res = await fetch('/api/auth/login', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(credentials),
        })
        if (!res.ok) throw new Error('Неверный логин или пароль')
        const { user, token } = await res.json()
        localStorage.setItem('token', token)
        set({ user, token, loading: false, error: null })
      } catch (err) {
        update((s) => ({
          ...s,
          loading: false,
          error: err instanceof Error ? err.message : 'Ошибка входа',
        }))
      }
    },

    logout() {
      localStorage.removeItem('token')
      set({ user: null, token: null, loading: false, error: null })
    },
  }
}

export const auth = createAuthStore()
export const isAuthenticated = derived(auth, ($auth) => !!$auth.token)
export const currentUser = derived(auth, ($auth) => $auth.user)

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

// stores/time.ts
import { readable } from 'svelte/store'

// readable — внешний источник данных
export const currentTime = readable<Date>(new Date(), (set) => {
  const interval = setInterval(() => set(new Date()), 1000)
  return () => clearInterval(interval) // cleanup
})

// WebSocket-стор
export const livePrice = readable<number | null>(null, (set) => {
  const ws = new WebSocket('wss://api.example.com/price')
  ws.onmessage = (e) => set(JSON.parse(e.data).price)
  ws.onerror = () => set(null)
  return () => ws.close()
})

Async-данные с состоянием

// stores/products.ts
import { writable, derived } from 'svelte/store'

interface AsyncState<T> {
  data: T | null
  loading: boolean
  error: string | null
}

function createAsyncStore<T>() {
  const { subscribe, set, update } = writable<AsyncState<T>>({
    data: null,
    loading: false,
    error: null,
  })

  return {
    subscribe,

    async load(fetcher: () => Promise<T>) {
      update((s) => ({ ...s, loading: true, error: null }))
      try {
        const data = await fetcher()
        set({ data, loading: false, error: null })
      } catch (err) {
        update((s) => ({
          ...s,
          loading: false,
          error: err instanceof Error ? err.message : 'Ошибка загрузки',
        }))
      }
    },

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

export const productsStore = createAsyncStore<Product[]>()
export const products = derived(productsStore, ($s) => $s.data ?? [])
export const productsLoading = derived(productsStore, ($s) => $s.loading)
<script lang="ts">
  import { onMount } from 'svelte'
  import { productsStore, products, productsLoading } from '$lib/stores/products'

  onMount(() => {
    productsStore.load(() => fetch('/api/products').then((r) => r.json()))
  })
</script>

{#if $productsLoading}
  <Spinner />
{:else}
  {#each $products as product (product.id)}
    <ProductCard {product} />
  {/each}
{/if}

Персистентность

// stores/theme.ts
import { writable } from 'svelte/store'

function persistedWritable<T>(key: string, initial: T) {
  const stored = localStorage.getItem(key)
  const value: T = stored ? JSON.parse(stored) : initial

  const store = writable<T>(value)

  store.subscribe((val) => {
    localStorage.setItem(key, JSON.stringify(val))
  })

  return store
}

export const theme = persistedWritable<'light' | 'dark'>('theme', 'light')
export const language = persistedWritable<string>('lang', 'ru')

Чтение стора вне компонента

import { get } from 'svelte/store'
import { auth } from '$lib/stores/auth'

// в API-клиенте
async function apiRequest(url: string, options: RequestInit = {}) {
  const { token } = get(auth)
  return fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      'Content-Type': 'application/json',
    },
  })
}

Тестирование

import { get } from 'svelte/store'
import { cart, cartTotal } from '../stores/cart'

beforeEach(() => cart.clear())

test('addItem добавляет товар', () => {
  cart.addItem({ id: '1', name: 'Test', price: 100 })
  expect(get(cart)).toHaveLength(1)
  expect(get(cartTotal)).toBe(100)
})

test('повторный addItem увеличивает quantity', () => {
  cart.addItem({ id: '1', name: 'Test', price: 100 })
  cart.addItem({ id: '1', name: 'Test', price: 100 })
  const items = get(cart)
  expect(items).toHaveLength(1)
  expect(items[0].quantity).toBe(2)
  expect(get(cartTotal)).toBe(200)
})

Структура файлов

src/lib/
  stores/
    auth.ts
    cart.ts
    ui.ts           # тема, язык, sidebar
    notifications.ts
    index.ts        # реэкспорт

Что делаем

Проектируем кастомные сторы под бизнес-логику приложения, реализуем async-паттерны, настраиваем персистентность, добавляем TypeScript-типизацию, покрываем тестами через Vitest.

Срок: 1–2 дня.