<?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 для оптимистичных мутаций в реальном времени]]></title><description><![CDATA[<p dir="auto"><img src="/assets/uploads/files/2b/08/c3/1773930585571-generated_1773930417642-resized.webp" alt="Обложка: Svelte 5: Runes с useOptimistic и TanStack Query для оптимистичных мутаций в реальном времени с rollback" class=" img-fluid img-markdown" /></p>
<p dir="auto">Svelte 5 принёс радикальные изменения в реактивность - и это не просто очередной мейджор апдейт. Теперь у нас есть <strong>руны</strong>, которые переносят логику состояния прямо в компонент без лишних обёрток. Это открывает невероятные возможности для реализации оптимистичных мутаций, которые раньше требовали жонглирования состояниями и сложной синхронизацией.</p>
<p dir="auto">Если ты когда-нибудь писала код, где пользователь кликает, UI обновляется, а потом ждёшь ответа с сервера - ты знаешь эту боль. Оптимистичные обновления решают эту проблему: меняешь интерфейс <em>сейчас</em>, а не когда бекенд решит ответить. В этой статье разберёмся, как Svelte 5 рунами и TanStack Query делают это elegantly.</p>
<h2>Что такое руны и почему они меняют игру</h2>
<p dir="auto">Руны в Svelte 5 - это по сути <strong>магические функции-компилятор-сигналы</strong>, которые влияют на работу компилятора напрямую. Вместо того чтобы писать громоздкий код с <code>useState</code> или <code>useReducer</code>, ты просто пишешь обычный JavaScript, а компилятор сам понимает, где нужна реактивность.</p>
<p dir="auto">В предыдущих версиях Svelte для создания реактивного состояния ты писала простой <code>let</code>, и компилятор это понимал. Но в Svelte 5 пошли дальше - теперь у тебя есть явные руны вроде <code>$state</code>, <code>$derived</code>, <code>$effect</code>, которые дают больше контроля и предсказуемости. Это не просто синтаксический сахар - это фундаментальный сдвиг в архитектуре реактивности.</p>
<p dir="auto">Ключевые руны для работы с состоянием:</p>
<ul>
<li><strong>$state</strong> - создаёт реактивное состояние, которое компилятор будет отслеживать. Можешь использовать примитивы или объекты - всё равно работает;</li>
<li><strong>$derived</strong> - вычисляемое состояние, которое автоматически обновляется, когда меняются зависимости (аналог <code>useMemo</code>);</li>
<li><strong>$effect</strong> - побочные эффекты, которые запускаются при изменении зависимостей (аналог <code>useEffect</code>);</li>
<li><strong>$props</strong> - явная передача пропсов в компонент с типизацией.</li>
</ul>
<p dir="auto">Зачем это нужно? Потому что <strong>мелкозернистая реактивность</strong> - это киллер-фича. Svelte 5 использует сигналы для реактивности, как и Solid.js. Это означает, что если в большом списке из 1000 элементов изменится один - перерисуется только он, а не весь компонент. Производительность просто зашкаливает.</p>
<h2>$state для оптимистичных обновлений</h2>
<p dir="auto">Теперь к самому интересному. Для оптимистичных мутаций нужно управлять несколькими состояниями одновременно: локальное состояние пользователя, статус загрузки, возможный откат. Раньше это была боль - нужно было синхронизировать множество <code>useState</code> хуков.</p>
<p dir="auto">С Svelte 5 это становится элегантнее. Вот как выглядит базовая структура:</p>
<pre><code class="language-javascript">&lt;script&gt;
  let todos = $state([]);
  let isLoading = $state(false);
  let error = $state(null);

  async function addTodoOptimistic(text) {
    const optimisticTodo = {
      id: Math.random(),
      text,
      pending: true
    };

    // Оптимистичное обновление - сразу показываем пользователю
    todos.push(optimisticTodo);
    isLoading = true;

    try {
      const response = await fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify({ text })
      });

      if (!response.ok) throw new Error('Ошибка сервера');

      const newTodo = await response.json();
      // Заменяем оптимистичное на реальное
      todos = todos.map(t =&gt; 
        t.id === optimisticTodo.id ? newTodo : t
      );
    } catch (err) {
      // Откат при ошибке
      todos = todos.filter(t =&gt; t.id !== optimisticTodo.id);
      error = err.message;
    } finally {
      isLoading = false;
    }
  }
&lt;/script&gt;
</code></pre>
<p dir="auto">Это уже намного чище, чем в классическом React. Но подожди, мы можем ещё лучше!</p>
<h2>TanStack Query + Svelte 5 рунами</h2>
<p dir="auto">TanStack Query (бывший React Query) - это <strong>супер-библиотека для управления асинхронным состоянием</strong>. Она берёт на себя кеширование, синхронизацию, инвалидацию, и предоставляет удобный API для работы с этим.</p>
<p dir="auto">Свежая новость - TanStack Query работает не только с React. Есть адаптер для Svelte, который отлично интегрируется с рунами. Вот как это выглядит в Svelte 5:</p>
<pre><code class="language-javascript">import { createMutation, useQueryClient } from '@tanstack/svelte-query';

let queryClient = useQueryClient();

const mutation = createMutation({
  mutationFn: async (newTodo) =&gt; {
    const res = await fetch('/api/todos', {
      method: 'POST',
      body: JSON.stringify(newTodo)
    });
    return res.json();
  },
  onMutate: async (newTodo) =&gt; {
    // Отменяем любые запросы на получение todos
    await queryClient.cancelQueries({ queryKey: ['todos'] });

    // Сохраняем старые данные для отката
    const previousTodos = queryClient.getQueryData(['todos']);

    // Оптимистичное обновление кеша
    queryClient.setQueryData(['todos'], (old) =&gt; [
      ...old,
      { ...newTodo, id: Date.now(), isPending: true }
    ]);

    return { previousTodos };
  },
  onError: (err, newTodo, context) =&gt; {
    // Откат на старые данные при ошибке
    queryClient.setQueryData(['todos'], context.previousTodos);
  },
  onSuccess: (data) =&gt; {
    // Инвалидируем кеш, чтобы получить свежие данные
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  }
});
</code></pre>
<p dir="auto">Видишь, как это работает? TanStack Query берёт на себя:**</p>
<ul>
<li><strong>onMutate</strong> - предварительная обработка перед запросом (оптимистичное обновление);</li>
<li><strong>onError</strong> - откат при ошибке;</li>
<li><strong>onSuccess</strong> - что делать после успешного ответа;</li>
<li><strong>Кеширование</strong> - автоматическая синхронизация данных между запросами.</li>
</ul>
<p dir="auto">Это жорстко лучше, чем писать всё вручную. TanStack Query заботится о граничных случаях, которые ты забудешь учесть.</p>
<h2>useOptimistic для UI-паттернов</h2>
<p dir="auto">Хотя в Svelte 5 нет встроенного <code>useOptimistic</code> (как в React 19), ты можешь легко создать свой хук, используя руны. Идея в том, что ты показываешь оптимистичное состояние UI, пока идёт запрос, а потом синхронизируешь с реальностью.</p>
<p dir="auto">Вот паттерн, который работает отлично:</p>
<pre><code class="language-javascript">&lt;script&gt;
  import { createMutation } from '@tanstack/svelte-query';

  let todos = $state([]);
  let optimisticTodos = $derived.by(() =&gt; {
    return todos.map(t =&gt; ({
      ...t,
      isOptimistic: !t.confirmed
    }));
  });

  const deleteMutation = createMutation({
    mutationFn: (id) =&gt; fetch(`/api/todos/${id}`, { method: 'DELETE' }),
    onMutate: (id) =&gt; {
      // Мгновенно удаляем из UI
      todos = todos.filter(t =&gt; t.id !== id);
      return id;
    },
    onError: (err, id, context) =&gt; {
      // Возвращаем назад, если ошибка
      todos.push({ id, text: 'Ошибка удаления', confirmed: false });
    }
  });

  function handleDeleteClick(id) {
    deleteMutation.mutate(id);
  }
&lt;/script&gt;

&lt;ul&gt;
  {#each optimisticTodos as todo (todo.id)}
    &lt;li class:pending={todo.isOptimistic}&gt;
      {todo.text}
      &lt;button on:click={() =&gt; handleDeleteClick(todo.id)}&gt;
        Удалить
      &lt;/button&gt;
    &lt;/li&gt;
  {/each}
&lt;/ul&gt;

&lt;style&gt;
  .pending {
    opacity: 0.6;
    text-decoration: line-through;
  }
&lt;/style&gt;
</code></pre>
<p dir="auto">Видишь, как <code>$derived</code> помогает? Это вычисляемое состояние автоматически обновляется, когда меняется <code>todos</code>. Никаких лишних рендеров, всё мелкозернисто.</p>
<h2>Сравнение подходов</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Подход</th>
<th>Плюсы</th>
<th>Минусы</th>
<th>Когда использовать</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ручное управление состоянием</td>
<td>Полный контроль, минимальные зависимости</td>
<td>Много кода, легко ошибиться, hard to debug</td>
<td>Простые формы, прототипы</td>
</tr>
<tr>
<td>TanStack Query + Svelte 5</td>
<td>Кеширование, синхронизация, встроенная обработка ошибок</td>
<td>Ещё одна зависимость, learning curve</td>
<td>Production-приложения, сложные данные</td>
</tr>
<tr>
<td>Combobox: Руны + собственный хук</td>
<td>Легковесно, всё под контролем, можешь использовать TypeScript</td>
<td>Нужно тестировать, поддерживать</td>
<td>Специфичные кейсы, микро-оптимизации</td>
</tr>
</tbody>
</table>
<h2>Практические советы и пидоры</h2>
<p dir="auto">Когда ты работаешь с оптимистичными обновлениями, есть несколько <strong>подводных камней</strong>, в которые легко наступить:</p>
<ul>
<li><strong>Конфликты идентификаторов</strong> - если генерируешь ID на клиенте (например, <code>Math.random()</code>), они могут конфликтовать. Лучше используй UUID или синхронизируй ID после успешного создания на сервере;</li>
<li><strong>Race conditions</strong> - если пользователь отправляет запрос, а потом тут же отправляет второй, нужно убедиться, что они применяются в правильном порядке. TanStack Query это обрабатывает, но собственный код - может упасть;</li>
<li><strong>Offline-режим</strong> - если соединение порвалось, мутация повиснет. Нужно обработать timeout и предложить пользователю повторить;</li>
<li><strong>Visibility state</strong> - используй <code>document.visibilityState</code> для отслеживания, виден ли пользователю вкладка. Если невидима - можешь отложить рефетч.</li>
</ul>
<p dir="auto">Продвинутый паттерн - <strong>оптимистичное обновление с очередью мутаций</strong>:</p>
<pre><code class="language-javascript">let mutationQueue = $state([]);
let isProcessing = $state(false);

async function processMutationQueue() {
  if (isProcessing || mutationQueue.length === 0) return;
  
  isProcessing = true;
  while (mutationQueue.length &gt; 0) {
    const mutation = mutationQueue.shift();
    try {
      await mutation.fn();
    } catch (err) {
      // Откат для этой мутации
      mutation.rollback?.();
    }
  }
  isProcessing = false;
}
</code></pre>
<p dir="auto">Это гарантирует, что мутации выполняются по очереди, даже если пользователь спамит кнопку.</p>
<h2>Поспешу наслаждаться результатом</h2>
<p dir="auto">Svelte 5 с рунами переносит нас в будущее, где реактивность - это не магия, а явный контроль. TanStack Query превращает сложное управление асинхронным состоянием в простую конфигурацию. Вместе они создают <strong>идеальную среду для оптимистичных мутаций</strong>, которые раньше требовали уйму boilerplate.</p>
<p dir="auto">Остаётся только помнить про edge cases и не забыть про типизацию TypeScript - и тогда твой код будет не просто работающим, но и красивым. Код, который хочется перечитывать, а не от него бежать.</p>
]]></description><link>https://forum.exlends.ru/topic/1767/svelte-5-runes-dlya-optimistichnyh-mutacij-v-realnom-vremeni</link><generator>RSS for Node</generator><lastBuildDate>Sat, 17 Oct 2026 04:06:18 GMT</lastBuildDate><atom:link href="https://forum.exlends.ru/topic/1767.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 19 Mar 2026 14:29:46 GMT</pubDate><ttl>60</ttl></channel></rss>