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

В модели связывания данных D3.js ключевым механизмом является тройка enter – update – exit, определяющая жизненный цикл визуальных элементов относительно набора данных. Exit-подвыборка отвечает за ситуацию, когда элементов DOM больше, чем элементов данных, и необходимо корректно удалить лишние узлы.

Механика data join и появление exit

Связывание данных в D3 строится вокруг метода .data(), который сопоставляет массив данных с выборкой DOM-элементов. Результатом этого сопоставления является объект join, содержащий три логические подвыборки:

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

Exit-подвыборка формируется автоматически, когда длина текущей выборки DOM превышает длину входного массива данных.

const update = d3.select("ul")
  .selectAll("li")
  .data(data);

После выполнения .data(data) D3 сравнивает старую и новую длину массива и формирует update.enter() и update.exit().


Получение exit-подвыборки

Exit-выборка извлекается через метод .exit():

const exitSelection = d3.select("ul")
  .selectAll("li")
  .data(data)
  .exit();

На этом этапе exitSelection содержит DOM-элементы, которые больше не связаны с данными.


Удаление элементов через remove()

Основная операция для exit-подвыборки — удаление узлов из DOM с помощью .remove():

d3.select("ul")
  .selectAll("li")
  .data(data)
  .exit()
  .remove();

Метод .remove() удаляет каждый элемент exit-подвыборки из DOM, полностью очищая визуализацию от устаревших данных.


Полный цикл обновления: enter + update + exit

Корректная работа с динамическими данными требует обработки всех трёх подвыборок.

const selection = d3.select("ul")
  .selectAll("li")
  .data(data);

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

// UPDATE
selection
  .text(d => d);

// ENTER
selection.enter()
  .append("li")
  .text(d => d);

Такой порядок гарантирует:

  • удаление лишних элементов
  • обновление существующих
  • добавление новых

Паттерн merge для объединения enter и update

В более современных подходах D3 используется метод .merge() для объединения enter и update-подвыборок в единый поток обновления.

const selection = d3.select("ul")
  .selectAll("li")
  .data(data);

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

// ENTER + UPDATE
selection.enter()
  .append("li")
  .merge(selection)
  .text(d => d);

Exit-логика остаётся отдельной, так как её задача — исключительно удаление.


Exit при уменьшении данных

Exit-подвыборка проявляется только при уменьшении количества элементов данных.

Пример сценария

Исходное состояние:

data = [10, 20, 30, 40, 50];

DOM содержит 5 элементов <li>.

После обновления:

data = [10, 20];

D3 формирует:

  • update: первые 2 элемента
  • exit: последние 3 элемента
selection.exit().remove();

Результат — DOM синхронизирован с данными.


Exit и ключевые функции (key function)

Без ключевой функции D3 сопоставляет данные по индексу. Это может приводить к неожиданному удалению или сохранению элементов при перестановке данных.

const selection = d3.select("ul")
  .selectAll("li")
  .data(data, d => d.id);

Поведение с key function:

  • идентичные id сохраняются
  • exit применяется только к реально исчезнувшим объектам
  • минимизируются лишние удаления и пересоздания DOM

Exit в SVG-визуализациях

Exit-подвыборка особенно важна при работе с SVG, где каждый элемент может быть сложной графической сущностью.

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

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

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

  • сначала радиус уменьшается до 0
  • затем элемент удаляется из DOM

Анимация удаления элементов

Exit-подвыборка может использоваться совместно с transition() для плавного исчезновения элементов.

selection.exit()
  .transition()
  .duration(500)
  .style("opacity", 0)
  .remove();

Такой подход позволяет:

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

Exit и сложные структуры данных

При работе с вложенными структурами (например, диаграммы, деревья, графы) exit-логика применяется рекурсивно.

Пример удаления узлов дерева:

const nodes = svg.selectAll("g.node")
  .data(treeData, d => d.id);

nodes.exit()
  .transition()
  .duration(400)
  .attr("transform", d => `translate(${d.x},${d.y})`)
  .style("opacity", 0)
  .remove();

Удаление здесь затрагивает не только визуальный элемент, но и всю связанную группу SVG-узлов.


Типичные ошибки при работе с exit

1. Отсутствие .remove()

selection.exit();

Без .remove() DOM-элементы остаются в документе и не очищаются.


2. Неправильный порядок операций

selection.exit().text("removed");
selection.exit().remove();

После .remove() дальнейшие операции не имеют смысла.


3. Отсутствие key function

Без ключа:

.data(data)

D3 может некорректно сопоставлять элементы, что приводит к неожиданным exit-удалениям.


Exit как часть декларативного обновления

Exit-подвыборка является фундаментом декларативного подхода D3:

  • DOM не управляется вручную
  • состояние описывается через данные
  • изменения автоматически отражаются в визуализации

Exit гарантирует, что визуализация не накапливает устаревшие элементы и остаётся строго синхронизированной с источником данных.


Производительность exit-операций

Удаление большого количества элементов может быть затратным, особенно при сложных SVG-структурах.

Оптимизационные практики:

  • использование key function для минимизации лишних exit-операций
  • группировка DOM-узлов в <g> для уменьшения количества операций
  • анимация с ограниченной длительностью
  • избегание повторных .exit().remove() в одном цикле обновления

Exit в паттерне потоковых данных

При потоковых данных (streaming updates) exit используется для ограничения размера визуализации:

const maxPoints = 100;

data.push(newPoint);
data = data.slice(-maxPoints);

selection
  .data(data)
  .exit()
  .remove();

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


Взаимодействие exit с фильтрацией

При фильтрации данных exit автоматически отражает исключённые элементы.

const filtered = data.filter(d => d.value > 50);

selection
  .data(filtered, d => d.id)
  .exit()
  .remove();

Все элементы, не прошедшие фильтр, удаляются из DOM.


Итоговая роль exit-подвыборки в архитектуре D3

Exit-подвыборка обеспечивает строгую синхронизацию визуального состояния с данными, удаляя устаревшие элементы и предотвращая накопление лишнего DOM. Она является неотъемлемой частью data-driven подхода, в котором обновление интерфейса определяется изменением данных, а не ручным управлением элементами.