Оптимистичные обновления

Оптимистичные обновления (optimistic updates) — это подход, при котором интерфейс пользователя мгновенно отражает изменения данных, предполагая успешное выполнение операции на сервере, ещё до получения ответа от сервера. В контексте Shopify Draggable, это позволяет создавать плавный пользовательский опыт при перетаскивании элементов, минимизируя задержку между действием пользователя и визуальной реакцией.

Применение оптимистичных обновлений с 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 — асинхронная синхронизация с сервером.
  • Обработка ошибок позволяет откатить DOM в исходное состояние, если операция не удалась.

Использование события 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 обеспечивает плавное перемещение элемента по экрану.
  • Взаимодействие с mirror не влияет на реальные элементы в DOM до момента завершения drag-события.

Работа с коллекциями и асинхронным обновлением

Если используется коллекция элементов, важно хранить локальное состояние порядка, чтобы можно было безопасно откатить изменения при ошибке.

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]);
    });
  }
});

Особенности подхода:

  • Оптимистичное обновление применяется к локальной копии состояния, чтобы минимизировать зависимость от серверного ответа.
  • Синхронизация с сервером выполняется пакетно, что уменьшает количество HTTP-запросов при множественных перетаскиваниях.
  • Откат изменений осуществляется через восстановление локального состояния, а не перезагрузку страницы.

Плюсы оптимистичных обновлений с Draggable

  1. Мгновенный отклик интерфейса — пользователь видит перемещение элемента без задержек.
  2. Снижение нагрузки на сервер — возможность пакетной отправки изменений.
  3. Гибкость в обработке ошибок — DOM и локальное состояние можно откатить без потери данных.
  4. Плавная анимация — mirror элементы позволяют создать естественное перемещение даже при медленном соединении.

Практические рекомендации

  • Всегда хранить оригинальные индексы элементов для корректного отката.
  • Использовать mirror элементы для управления визуальным отображением без изменения DOM до завершения операции.
  • Для сложных коллекций применять локальное состояние массива объектов, чтобы отслеживать порядок элементов независимо от DOM.
  • Оборачивать асинхронные серверные вызовы в try/catch для безопасного восстановления интерфейса.

Оптимистичные обновления в сочетании с Shopify Draggable позволяют создавать динамичные, отзывчивые интерфейсы для сортируемых списков, карточек и других перетаскиваемых компонентов, обеспечивая одновременно плавность UX и надежность данных.