Svelte 5: Runes и useOptimistic для оптимистичных обновлений корзины rollback
-
Как в Svelte 5 с Runes можно реализовать оптимистичные обновления для корзины покупок. Это решает проблему лагов при добавлении товаров - UI обновляется мгновенно, а сервер подтверждает позже. Если что-то пойдет не так, делаем rollback без боли.
Под капотом useOptimistic из SvelteKit упрощает стейт-менеджмент, интегрируясь с runes. Получается чистый DX: меньше бойлерплейта, лучше Web Vitals. Показываю на примере корзины, где юзер добавляет айтем - и бац, он уже в списке, даже без ответа с бэка.
Runes в Svelte 5: $state и $derived для корзины
Runes - это киллер-фича Svelte 5, которая делает реактивность явной и мощной. Вместо скрытого стейта теперь $state для мутабельных данных, $derived для вычисляемых значений. В корзине это идеально: держим массив товаров в $state, а totalPrice - в $derived. На первый взгляд кажется просто, но под капотом runes компилируются в vanilla JS без overhead.
Представьте: юзер кликает ‘добавить в корзину’, стейт обновляется синхронно, рендер летает. Без runes пришлось бы тащить stores или props drilling - это просто боль. А здесь код изящный, типизированный на TS. Давайте разберем базовый сетап.
- $state для корзины:
let cart = $state<Product[]>([]);- мутируем напрямую, Svelte сам трекает изменения. - $derived для итога:
let total = $derived(cart.reduce((sum, item) => sum + item.price * item.qty, 0));- реактивно пересчитывается. - $effect для сайд-эффектов:
effect(() => { if (cart.length) saveToLocalStorage(cart); });- запускается при изменениях.
Свойство Runes Старый Svelte Реактивность Явная, компилируется в signal Implicit, через $: DX Меньше boilerplate Больше writable stores SSR Полная поддержка Частично Нюанс: в SSR $state инициализируется на сервере, hydrate на клиенте - никаких flickering.
useOptimistic: мгновенные обновления без race conditions
useOptimistic - хук из SvelteKit, который берет optimistic стейт и action. При клике на ‘добавить’ он сразу мутирует UI-стейт, параллельно шлет fetch на сервер. Если ответ ок - коммитим, если ошибка - rollback к предыдущему стейту. Это огонь для корзины: юзер видит товар instantly, LCP не страдает.
Под капотом происходит магия: хук создает shadow state на основе текущего, апплайит update function. Fetch идет с {optimistic: true}, сервер возвращает реальный стейт. Нет нужды в сложных reducer’ах как в Redux. В Svelte 5 это идеально сочетается с runes - $state остается чистым.
Вот короткий пример на TS:
<script> interface Product { id: string; name: string; price: number; qty: number; } let cart = $state<Product[]>([]); async function addToCart(product: Product) { const update = (cart: Product[]) => [...cart, {...product, qty: 1}]; const result = await useOptimistic(cart, update, async () => { const res = await fetch('/api/cart', { method: 'POST', body: JSON.stringify(product) }); return res.json(); }); cart = result; // коммит или rollback } </script>- Первый аргумент: текущий стейт (наш $state cart).
- Второй: updateFn - как изменить стейт оптимистично.
- Третий: action - реальный fetch с сервером.
Крутость в том, что рендер обновляется дважды: optimistic > confirmed.
Rollback при ошибках: graceful handling
Если сервер вернул 500 или товар out of stock - useOptimistic автоматически роллбэчит стейт. Никаких try/catch вручную, хук сам toast’ит ошибку или логирует. В корзине это спасает от ghost items: добавил 10 шт, сервер сказал ‘нет, только 5’ - qty корректируется плавно.
Добавим error handling:
function addToCart(product: Omit<Product, 'qty'>) { return useOptimistic( cart, (c) => [...c, { ...product, qty: 1 }], async () => { const res = await fetch('/api/cart', { method: 'POST', body: JSON.stringify(product) }); if (!res.ok) throw new Error('Out of stock'); return res.json(); }, { onError: (err) => toast.error(err.message) } ); }Сценарий Без useOptimistic С useOptimistic Успех Двойной рендер Один seamless Ошибка Ручной rollback Авто + toast Network Блокировка UI Instant feedback Важно: updateFn должен быть pure - без мутаций, возвращать new state.
Полный пример: корзина с SSR и runes
Склеим все: page.svelte с корзиной, +page.server.ts для API. На сервере рендерим initial cart из cookies или DB. Клиент hydrate’ит runes, useOptimistic берет на себя updates. Бандл минимальный, нет hydration mismatch.
Сервер:
// +page.server.ts export async function load() { const cart = await getCartFromDB(); return { cart }; } export const actions = { default: async ({ request }) => { const form = await request.formData(); // update DB return { success: true, cart: updatedCart }; } };Клиент рендерит streamed, optimistic работает из коробки. Total derived обновляется reactive.
- Преимущества: CLS = 0, FID низкий, SEO-friendly SSR.
- Нюансы: Для сложных updates делай batching в updateFn.
- Оптимизации: Memoize derived с $derived.by().
В итоге корзина флайтит даже на 3G - чистая магия Svelte 5.
Масштабирование: от корзины к сложным формам
Это не только для cart - useOptimistic тащит likes, comments, infinite lists. С runes стейт глобальный без контекста, легко шарить между компонентами. DX на уровне: линтер happy, TS strict, бандл lean.
Осталось за кадром: интеграция с TanStack Query для кэша, или WebSockets для real-time. Подумать стоит над custom runes для offline-first apps - Svelte 5 дает фундамент. Код красивый, производительный, готов к продакшену.
- $state для корзины:
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.