Mutation Observer

Mutation Observer — это механизм отслеживания изменений в DOM, предоставляющий высокую производительность по сравнению с устаревшими событиями типа DOMSubtreeModified. В библиотеке Atomico он используется для реактивного реагирования на изменения элементов и их атрибутов, что особенно важно при построении веб-компонентов с динамическим поведением.


Основы использования

В Atomico useMutationObserver является хуком, который позволяет подписываться на изменения DOM-узлов внутри компонента. Сигнатура функции выглядит следующим образом:

import { useMutationObserver } from "atomico";

function MyComponent() {
    const ref = useRef();

    useMutationObserver(ref, (mutations) => {
        for (const mutation of mutations) {
            console.log(mutation.type, mutation.target);
        }
    }, { attributes: true, childList: true, subtree: true });

    return <div ref={ref}>Контент</div>;
}

Ключевые моменты:

  • ref — ссылка на DOM-элемент, за изменениями которого необходимо наблюдать.

  • Колбек вызывается при любом изменении, указанном в конфигурации.

  • Третий аргумент — объект MutationObserverInit, где можно включить наблюдение за:

    • attributes — изменения атрибутов.
    • childList — добавление или удаление дочерних элементов.
    • subtree — наблюдение за всеми вложенными элементами.
    • characterData — изменения текстового содержимого.

Настройка наблюдателя

Объект конфигурации позволяет точно указать, какие изменения отслеживать:

{
    attributes: true,      // Отслеживание атрибутов
    attributeFilter: ["class", "style"], // Только указанные атрибуты
    childList: true,       // Добавление или удаление дочерних элементов
    subtree: false,        // Вложенные элементы не отслеживаются
    characterData: true    // Изменение текста внутри элементов
}

Использование attributeFilter помогает снизить нагрузку, отслеживая только необходимые атрибуты.


Применение в реактивных компонентах

В Atomico наблюдатели часто используются совместно с реактивными свойствами и состояниями. Например, можно обновлять состояние компонента при добавлении новых элементов:

import { c, useState, useMutationObserver } from "atomico";

function TodoList() {
    const [count, setCount] = useState(0);
    const ref = useRef();

    useMutationObserver(ref, (mutations) => {
        const addedNodes = mutations.flatMap(m => Array.from(m.addedNodes));
        if (addedNodes.length) setCount(count + addedNodes.length);
    }, { childList: true, subtree: true });

    return (
        <host>
            <div ref={ref}>
                <slot></slot>
            </div>
            <p>Количество элементов: {count}</p>
        </host>
    );
}

customElements.define("todo-list", c(TodoList));

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

  • MutationObserver позволяет компоненту реагировать на изменения слота, даже если добавление происходит извне.
  • Состояние count автоматически синхронизируется с количеством дочерних элементов.

Управление жизненным циклом

Atomico интегрирует наблюдателей с жизненным циклом компонента. Наблюдатель автоматически отключается при размонтировании, если используется хук useMutationObserver, что предотвращает утечки памяти.

При ручном создании наблюдателя можно использовать стандартный API:

const observer = new MutationObserver(callback);
observer.observe(element, { childList: true });

// В момент размонтирования
observer.disconnect();

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

  1. Минимизация нагрузки: наблюдать только за необходимыми атрибутами и дочерними элементами.
  2. Сжатие вызовов колбека: изменения часто приходят пачками, стоит обрабатывать их вместе.
  3. Поддержка слотов: для веб-компонентов слоты требуют subtree: true, чтобы отслеживать внешние вставки.
  4. Совмещение с реактивными состояниями: использовать useState или useReducer для автоматического обновления интерфейса.

Примеры сложных сценариев

Отслеживание атрибутов и текста одновременно

useMutationObserver(ref, (mutations) => {
    mutations.forEach(mutation => {
        if (mutation.type === "attributes") {
            console.log("Атрибут изменен:", mutation.attributeName);
        }
        if (mutation.type === "characterData") {
            console.log("Текст изменен:", mutation.target.data);
        }
    });
}, { attributes: true, characterData: true, subtree: true });

Отслеживание динамического списка с фильтрацией

useMutationObserver(ref, (mutations) => {
    const newItems = mutations
        .flatMap(m => Array.from(m.addedNodes))
        .filter(node => node.tagName === "LI");
    if (newItems.length) console.log("Добавлены новые элементы списка:", newItems);
}, { childList: true, subtree: true });

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


Вывод

MutationObserver в Atomico является мощным инструментом для реактивной работы с DOM. Он позволяет отслеживать изменения в атрибутах, тексте и структуре элементов, интегрируется с хуками и состояниями компонентов, обеспечивает высокую производительность и предотвращает утечки памяти при правильном использовании. В сочетании с реактивными механизмами Atomico он становится фундаментом для построения сложных и динамических веб-компонентов.