Оптимистичные обновления (optimistic updates) — это подход, при котором интерфейс пользователя мгновенно отражает изменения данных, предполагая успешное выполнение операции на сервере, ещё до получения ответа от сервера. В контексте Shopify Draggable, это позволяет создавать плавный пользовательский опыт при перетаскивании элементов, минимизируя задержку между действием пользователя и визуальной реакцией.
В Draggable основной сценарий использования
оптимистичных обновлений связан с событиями drag:start,
drag:move, drag:stop,
sortable:sorted и sortable:stop. Ключевой
принцип — обновление DOM сразу после изменения позиции
элемента, а синхронизация с сервером выполняется
асинхронно.
import { Draggable } from '@shopify/draggable';
const container = document.querySelector('.list');
const draggable = new Draggable(container, {
draggable: '.list-item',
mirror: {
constrainDimensions: true
}
});
draggable.on('sortable:stop', async (event) => {
const item = event.data.dragEvent.source;
const newIndex = Array.from(container.children).indexOf(item);
// Оптимистичное обновление интерфейса
item.dataset.index = newIndex;
try {
await fetch('/update-item-order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: item.dataset.id, position: newIndex })
});
} catch (error) {
// Откат изменений при ошибке сервера
const originalIndex = parseInt(item.dataset.originalIndex);
container.insertBefore(item, container.children[originalIndex]);
}
});
Ключевые моменты:
item.dataset.index — локальное обновление позиции
элемента без ожидания ответа сервера.fetch — асинхронная синхронизация с сервером.drag:mirror:created для плавного UXОптимистичные обновления выигрывают от работы с зеркалом перетаскиваемого элемента (mirror element), чтобы перемещение выглядело естественно, даже если серверная синхронизация задерживается.
draggable.on('drag:mirror:created', (event) => {
const mirror = event.data.mirror;
mirror.style.transition = 'transform 0.2s ease';
});
mirror.style.transition обеспечивает плавное
перемещение элемента по экрану.Если используется коллекция элементов, важно хранить локальное состояние порядка, чтобы можно было безопасно откатить изменения при ошибке.
const itemsState = Array.from(container.children).map((el, index) => ({
id: el.dataset.id,
index
}));
draggable.on('sortable:stop', async (event) => {
const item = event.data.dragEvent.source;
const oldIndex = itemsState.find(el => el.id === item.dataset.id).index;
const newIndex = Array.from(container.children).indexOf(item);
// Локальное обновление
itemsState.forEach(el => {
if (el.index >= newIndex && el.id !== item.dataset.id) el.index++;
});
itemsState.find(el => el.id === item.dataset.id).index = newIndex;
try {
await fetch('/update-order', {
method: 'POST',
body: JSON.stringify(itemsState)
});
} catch (error) {
// Откат локального состояния и DOM
itemsState.forEach((el, idx) => {
const domEl = container.querySelector(`[data-id="${el.id}"]`);
container.insertBefore(domEl, container.children[idx]);
});
}
});
Особенности подхода:
try/catch
для безопасного восстановления интерфейса.Оптимистичные обновления в сочетании с Shopify Draggable позволяют создавать динамичные, отзывчивые интерфейсы для сортируемых списков, карточек и других перетаскиваемых компонентов, обеспечивая одновременно плавность UX и надежность данных.