Паттерн enter-update-exit: теория и смысл

В основе модели обновления данных в D3.js лежит концепция связывания данных с DOM-элементами. Этот процесс называется data join и формально описывает соответствие между массивом данных и набором элементов в DOM. При каждом обновлении визуализации библиотека сравнивает:

  • текущий набор данных
  • текущие DOM-элементы

Результатом становится разделение на три логические группы:

  • enter — данные без соответствующих DOM-элементов
  • update — данные, которые уже связаны с существующими элементами
  • exit — DOM-элементы, для которых больше нет данных

Эта модель позволяет управлять изменениями декларативно, не пересоздавая весь DOM при каждом обновлении.


Базовая модель связывания данных

Операция связывания выполняется через метод selection.data():

const circles = svg.selectAll("circle")
  .data(data);

На этом этапе D3 не изменяет DOM, а только устанавливает соответствие между:

  • индексами массива данных
  • выбранными DOM-узлами

Возвращаемое значение содержит внутреннюю структуру с тремя подселекциями:

  • enter()
  • обновлённая selection (update)
  • exit()

Enter-подвыборка: создание новых элементов

Enter-подвыборка содержит фиктивные элементы, представляющие данные, для которых ещё нет DOM-узлов.

Типичный паттерн:

const circles = svg.selectAll("circle")
  .data(data);

circles.enter()
  .append("circle")
  .attr("r", 5);

Механика:

  • каждый элемент enter() соответствует одному новому элементу данных
  • append() создаёт реальные DOM-узлы
  • после append() элементы становятся частью основной selection

Особенность enter-селекции заключается в том, что она не содержит реальных DOM-элементов до момента их создания.


Update-подвыборка: обновление существующих элементов

Update — это основная selection, возвращаемая после .data().

const circles = svg.selectAll("circle")
  .data(data);

circles
  .attr("fill", "steelblue")
  .attr("r", d => d.value);

В update-подвыборке находятся элементы, которые уже существуют в DOM и были сопоставлены с новыми данными.

Характерные особенности:

  • применяется к пересечению старого и нового набора данных
  • используется для обновления атрибутов, стилей, трансформаций
  • не создаёт и не удаляет элементы

Exit-подвыборка: удаление лишних элементов

Exit-подвыборка содержит DOM-элементы, для которых отсутствуют соответствующие данные.

circles.exit()
  .remove();

Логика работы:

  • D3 определяет элементы без пары в новом массиве данных
  • такие элементы помещаются в exit
  • обычно к ним применяется .remove()

Exit — ключевой механизм поддержания согласованности визуализации с данными.


Полный цикл enter-update-exit

Типичный полный шаблон обновления:

const circles = svg.selectAll("circle")
  .data(data);

// EXIT
circles.exit()
  .remove();

// UPDATE
circles
  .attr("fill", "steelblue")
  .attr("r", d => d.value);

// ENTER
circles.enter()
  .append("circle")
  .attr("fill", "green")
  .attr("r", d => d.value)
  .merge(circles);

Важно разделение ответственности:

  • exit — удаление
  • update — изменение существующих
  • enter — создание новых

Merge и объединение enter и update

Метод merge() объединяет enter и update selections в единый поток обновления.

const circles = svg.selectAll("circle")
  .data(data);

const circlesEnter = circles.enter()
  .append("circle");

circlesEnter.merge(circles)
  .attr("r", d => d.value);

Смысл:

  • enter и update часто требуют одинаковых атрибутов
  • merge позволяет избежать дублирования кода
  • итоговая selection содержит все актуальные элементы

Ключи (key function) и стабильность привязки

По умолчанию D3 сопоставляет данные по индексу массива. Это может приводить к некорректным обновлениям при изменении порядка элементов.

Для стабильной идентификации используется key function:

const circles = svg.selectAll("circle")
  .data(data, d => d.id);

Роль key function:

  • определяет уникальность данных
  • предотвращает “перепутывание” элементов
  • улучшает корректность enter/exit логики

При наличии ключей D3 сравнивает не позиции, а идентификаторы.


Общая схема жизненного цикла элементов

Модель можно представить как последовательность:

  1. Исходное состояние DOM

  2. Новый массив данных

  3. Сопоставление data join

  4. Разделение:

    • enter → создание
    • update → изменение
    • exit → удаление

Это превращает визуализацию в функцию состояния:

UI = f(data)


Паттерн general update

В сложных визуализациях применяется структурированный подход, известный как general update pattern:

function update(data) {
  const circles = svg.selectAll("circle")
    .data(data, d => d.id);

  circles.exit()
    .transition()
    .attr("r", 0)
    .remove();

  circles.transition()
    .attr("fill", "steelblue")
    .attr("r", d => d.value);

  circles.enter()
    .append("circle")
    .attr("r", 0)
    .attr("fill", "green")
    .merge(circles)
    .transition()
    .attr("r", d => d.value);
}

Ключевые характеристики:

  • единая функция обновления состояния
  • явное разделение enter/update/exit
  • возможность анимации переходов на каждом этапе

Порядок вызовов и внутреннее устройство

Внутри D3 последовательность операций критична:

  • .data() создаёт привязку
  • .enter() вычисляется относительно отсутствующих узлов
  • .exit() вычисляется как обратное множество
  • update selection остаётся основной

Важно, что enter и exit не существуют как DOM-элементы до применения операций создания или удаления.


Поведение при изменении размера данных

Рассмотрим три типичных случая:

Увеличение массива данных

  • появляются новые элементы в enter
  • update остаётся частичным
  • exit пуст

Уменьшение массива данных

  • часть элементов попадает в exit
  • остальные обновляются

Полная замена данных

  • почти всё распределяется между enter и exit
  • update минимален или отсутствует

Связь с декларативной моделью

Enter-update-exit формирует переходную модель между императивным DOM и декларативным описанием данных:

  • данные описывают состояние
  • DOM отражает это состояние
  • D3 обеспечивает синхронизацию

Эта модель позволяет строить визуализации, где изменения данных автоматически транслируются в структурные изменения интерфейса без ручного управления каждым DOM-узлом.