Optimistic UI — это подход к построению интерфейса, при котором изменения отображаются пользователю мгновенно, ещё до того, как сервер подтвердит их успешное выполнение. Такой подход повышает ощущение отзывчивости приложения и улучшает пользовательский опыт, особенно в приложениях с высокой интерактивностью, таких как чаты, доски задач или социальные сети.
В SvelteKit реализация Optimistic UI строится на комбинации
реактивности Svelte и асинхронных вызовов через fetch,
actions или внешние API-клиенты. Ключевая идея заключается
в моментальной локальной модификации состояния, с
последующей синхронизацией с сервером.
Для оптимистичного обновления критично правильно организовать состояние. В SvelteKit чаще всего применяются:
writable,
derived) для глобального состояния.Пример использования writable для списка задач с
оптимистичным добавлением:
import { writable } from 'svelte/store';
export const tasks = writable([]);
export async function addTaskOptimistic(newTask) {
// Создаём временный объект с уникальным ID
const tempTask = { ...newTask, id: Date.now(), pending: true };
tasks.update(ts => [tempTask, ...ts]);
try {
const response = await fetch('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newTask)
});
const savedTask = await response.json();
// Заменяем временный объект на сохранённый сервером
tasks.update(ts => ts.map(t => t.id === tempTask.id ? savedTask : t));
} catch (err) {
// Откатываем изменение при ошибке
tasks.update(ts => ts.filter(t => t.id !== tempTask.id));
console.error('Ошибка добавления задачи:', err);
}
}
Ключевые моменты этого подхода:
store.pending: true позволяет визуально отобразить
статус ожидания.SvelteKit Actions позволяют обрабатывать формы на сервере, сохраняя реактивность на клиенте. Для оптимистичного поведения можно комбинировать Actions и локальные состояния:
<script lang="ts">
import { tasks } from './stores';
let newTaskText = '';
async function handleSubmit() {
const tempTask = { id: Date.now(), text: newTaskText, pending: true };
tasks.update(ts => [tempTask, ...ts]);
const formData = new FormData();
formData.append('text', newTaskText);
try {
const res = await fetch('/tasks', { method: 'POST', body: formData });
const savedTask = await res.json();
tasks.update(ts => ts.map(t => t.id === tempTask.id ? savedTask : t));
} catch (err) {
tasks.update(ts => ts.filter(t => t.id !== tempTask.id));
console.error(err);
} finally {
newTaskText = '';
}
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<input bind:value={newTaskText} placeholder="Добавить задачу" />
<button type="submit">Создать</button>
</form>
Особенности подхода через Actions:
pending статус позволяет визуально дифференцировать
новые элементы.При оптимистичных обновлениях необходимо учитывать:
Пример оптимистичного удаления элемента:
export async function deleteTaskOptimistic(taskId) {
let removedTask;
tasks.update(ts => {
removedTask = ts.find(t => t.id === taskId);
return ts.filter(t => t.id !== taskId);
});
try {
await fetch(`/api/tasks/${taskId}`, { method: 'DELETE' });
} catch (err) {
tasks.update(ts => [removedTask, ...ts]);
console.error('Ошибка удаления задачи:', err);
}
}
Для полноценного Optimistic UI важно отражать состояние операции:
{#each $tasks as task (task.id)}
<div class="{task.pending ? 'pending' : ''}">
{task.text}
</div>
{/each}
<style>
.pending {
opacity: 0.5;
font-style: italic;
}
</style>
SvelteKit легко комбинируется с UI-библиотеками вроде Shadcn/UI, Svelte Material UI, Carbon Components Svelte. В оптимистичных сценариях можно:
loading состоянием для
операций.Пример с Svelte Material UI:
<SMUIButton disabled={isPending} on:click={handleSubmit}>
{#if isPending}
Создание...
{:else}
Создать
{/if}
</SMUIButton>
pending, error, success.Такой подход позволяет создавать интерфейсы, которые чувствуются мгновенными, даже когда серверная часть обрабатывает действия пользователя асинхронно. В SvelteKit Optimistic UI реализуется естественно благодаря реактивности, локальным состояниям и гибкой работе с Stores.