<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Svelte 5: Runes и useOptimistic для оптимистичных обновлений корзины rollback]]></title><description><![CDATA[<p dir="auto">Как в Svelte 5 с Runes можно реализовать оптимистичные обновления для корзины покупок. Это решает проблему лагов при добавлении товаров - UI обновляется мгновенно, а сервер подтверждает позже. Если что-то пойдет не так, делаем rollback без боли.</p>
<p dir="auto">Под капотом useOptimistic из SvelteKit упрощает стейт-менеджмент, интегрируясь с runes. Получается чистый DX: меньше бойлерплейта, лучше Web Vitals. Показываю на примере корзины, где юзер добавляет айтем - и бац, он уже в списке, даже без ответа с бэка.</p>
<h2>Runes в Svelte 5: $state и $derived для корзины</h2>
<p dir="auto">Runes - это киллер-фича Svelte 5, которая делает реактивность явной и мощной. Вместо скрытого стейта теперь $state для мутабельных данных, $derived для вычисляемых значений. В корзине это идеально: держим массив товаров в $state, а totalPrice - в $derived. На первый взгляд кажется просто, но под капотом runes компилируются в vanilla JS без overhead.</p>
<p dir="auto">Представьте: юзер кликает ‘добавить в корзину’, стейт обновляется синхронно, рендер летает. Без runes пришлось бы тащить stores или props drilling - это просто боль. А здесь код изящный, типизированный на TS. Давайте разберем базовый сетап.</p>
<ul>
<li><strong>$state для корзины</strong>: <code>let cart = $state&lt;Product[]&gt;([]);</code> - мутируем напрямую, Svelte сам трекает изменения.</li>
<li><strong>$derived для итога</strong>: <code>let total = $derived(cart.reduce((sum, item) =&gt; sum + item.price * item.qty, 0));</code> - реактивно пересчитывается.</li>
<li><strong>$effect для сайд-эффектов</strong>: <code>effect(() =&gt; { if (cart.length) saveToLocalStorage(cart); });</code> - запускается при изменениях.</li>
</ul>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Свойство</th>
<th>Runes</th>
<th>Старый Svelte</th>
</tr>
</thead>
<tbody>
<tr>
<td>Реактивность</td>
<td>Явная, компилируется в signal</td>
<td>Implicit, через $:</td>
</tr>
<tr>
<td>DX</td>
<td>Меньше boilerplate</td>
<td>Больше writable stores</td>
</tr>
<tr>
<td>SSR</td>
<td>Полная поддержка</td>
<td>Частично</td>
</tr>
</tbody>
</table>
<p dir="auto"><em>Нюанс: в SSR $state инициализируется на сервере, hydrate на клиенте - никаких flickering.</em></p>
<h2>useOptimistic: мгновенные обновления без race conditions</h2>
<p dir="auto">useOptimistic - хук из SvelteKit, который берет optimistic стейт и action. При клике на ‘добавить’ он сразу мутирует UI-стейт, параллельно шлет fetch на сервер. Если ответ ок - коммитим, если ошибка - rollback к предыдущему стейту. Это огонь для корзины: юзер видит товар instantly, LCP не страдает.</p>
<p dir="auto">Под капотом происходит магия: хук создает shadow state на основе текущего, апплайит update function. Fetch идет с {optimistic: true}, сервер возвращает реальный стейт. Нет нужды в сложных reducer’ах как в Redux. В Svelte 5 это идеально сочетается с runes - $state остается чистым.</p>
<p dir="auto">Вот короткий пример на TS:</p>
<pre><code class="language-javascript">&lt;script&gt;
  interface Product { id: string; name: string; price: number; qty: number; }
  let cart = $state&lt;Product[]&gt;([]);
  
  async function addToCart(product: Product) {
    const update = (cart: Product[]) =&gt; [...cart, {...product, qty: 1}];
    const result = await useOptimistic(cart, update, async () =&gt; {
      const res = await fetch('/api/cart', { method: 'POST', body: JSON.stringify(product) });
      return res.json();
    });
    cart = result; // коммит или rollback
  }
&lt;/script&gt;
</code></pre>
<ul>
<li><strong>Первый аргумент</strong>: текущий стейт (наш $state cart).</li>
<li><strong>Второй</strong>: updateFn - как изменить стейт оптимистично.</li>
<li><strong>Третий</strong>: action - реальный fetch с сервером.</li>
</ul>
<p dir="auto"><strong>Крутость в том, что рендер обновляется дважды: optimistic &gt; confirmed.</strong></p>
<h3>Rollback при ошибках: graceful handling</h3>
<p dir="auto">Если сервер вернул 500 или товар out of stock - useOptimistic автоматически роллбэчит стейт. Никаких try/catch вручную, хук сам toast’ит ошибку или логирует. В корзине это спасает от ghost items: добавил 10 шт, сервер сказал ‘нет, только 5’ - qty корректируется плавно.</p>
<p dir="auto">Добавим error handling:</p>
<pre><code class="language-ts">function addToCart(product: Omit&lt;Product, 'qty'&gt;) {
  return useOptimistic(
    cart,
    (c) =&gt; [...c, { ...product, qty: 1 }],
    async () =&gt; {
      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) =&gt; toast.error(err.message) }
  );
}
</code></pre>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Сценарий</th>
<th>Без useOptimistic</th>
<th>С useOptimistic</th>
</tr>
</thead>
<tbody>
<tr>
<td>Успех</td>
<td>Двойной рендер</td>
<td>Один seamless</td>
</tr>
<tr>
<td>Ошибка</td>
<td>Ручной rollback</td>
<td>Авто + toast</td>
</tr>
<tr>
<td>Network</td>
<td>Блокировка UI</td>
<td>Instant feedback</td>
</tr>
</tbody>
</table>
<p dir="auto"><em>Важно: updateFn должен быть pure - без мутаций, возвращать new state.</em></p>
<h2>Полный пример: корзина с SSR и runes</h2>
<p dir="auto">Склеим все: page.svelte с корзиной, +page.server.ts для API. На сервере рендерим initial cart из cookies или DB. Клиент hydrate’ит runes, useOptimistic берет на себя updates. Бандл минимальный, нет hydration mismatch.</p>
<p dir="auto">Сервер:</p>
<pre><code class="language-ts">// +page.server.ts
export async function load() {
  const cart = await getCartFromDB();
  return { cart };
}

export const actions = {
  default: async ({ request }) =&gt; {
    const form = await request.formData();
    // update DB
    return { success: true, cart: updatedCart };
  }
};
</code></pre>
<p dir="auto">Клиент рендерит streamed, optimistic работает из коробки. Total derived обновляется reactive.</p>
<ul>
<li><strong>Преимущества</strong>: CLS = 0, FID низкий, SEO-friendly SSR.</li>
<li><strong>Нюансы</strong>: Для сложных updates делай batching в updateFn.</li>
<li><strong>Оптимизации</strong>: Memoize derived с $<a href="http://derived.by" target="_blank" rel="noopener noreferrer">derived.by</a>().</li>
</ul>
<p dir="auto"><strong>В итоге корзина флайтит даже на 3G - чистая магия Svelte 5.</strong></p>
<h2>Масштабирование: от корзины к сложным формам</h2>
<p dir="auto">Это не только для cart - useOptimistic тащит likes, comments, infinite lists. С runes стейт глобальный без контекста, легко шарить между компонентами. DX на уровне: линтер happy, TS strict, бандл lean.</p>
<p dir="auto">Осталось за кадром: интеграция с TanStack Query для кэша, или WebSockets для real-time. Подумать стоит над custom runes для offline-first apps - Svelte 5 дает фундамент. Код красивый, производительный, готов к продакшену.</p>
]]></description><link>https://forum.exlends.ru/topic/1756/svelte-5-runes-i-useoptimistic-dlya-optimistichnyh-obnovlenij-korziny-rollback</link><generator>RSS for Node</generator><lastBuildDate>Sat, 17 Oct 2026 03:08:05 GMT</lastBuildDate><atom:link href="https://forum.exlends.ru/topic/1756.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 19 Mar 2026 10:06:30 GMT</pubDate><ttl>60</ttl></channel></rss>