Reflect и синхронизация с атрибутами

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


Определение свойства с reflect

Свойства компонента объявляются с помощью функции prop из Atomico. Параметр reflect указывает, будет ли значение свойства автоматически отражаться в атрибуте DOM.

import { c, prop } from "atomico";

function myComponent({ color }) {
    return <host style={{ color }}></host>;
}

myComponent.props = {
    color: prop.string({ reflect: true })
};

customElements.define("my-component", c(myComponent));

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

  • reflect: true автоматически синхронизирует свойство color с одноимённым атрибутом.
  • При изменении свойства через JS, атрибут color в DOM обновляется.
  • При изменении атрибута в DOM (<my-component color="red"></my-component>), свойство color обновляется и компонент реагирует на это изменение.

Типы данных и отражение

Не все типы свойств могут быть напрямую отражены в атрибутах. Atomico поддерживает следующие типы с reflect:

  • String — напрямую синхронизируется с атрибутом.
  • Number — преобразуется в строку при записи в атрибут и обратно при чтении.
  • Boolean — наличие атрибута означает true, его отсутствие — false.
  • Array/Object — по умолчанию не поддерживается, требуется кастомное сериализуемое представление через get и set.

Пример для boolean-свойства:

myComponent.props = {
    visible: prop.boolean({ reflect: true })
};

// В DOM
// <my-component visible></my-component> => visible === true
// <my-component></my-component> => visible === false

Для чисел:

myComponent.props = {
    size: prop.number({ reflect: true })
};

// В DOM
// <my-component size="42"></my-component> => size === 42

Настройка кастомной сериализации

Если нужно синхронизировать более сложные данные (например, объект или массив), используется комбинация свойств get и set для кастомного преобразования:

myComponent.props = {
    data: prop({
        reflect: true,
        get: (value) => JSON.parse(value || "{}"),
        set: (value) => JSON.stringify(value)
    })
};

// <my-component data='{"x":10,"y":20}'></my-component>

Здесь объект автоматически преобразуется в строку для атрибута и обратно в объект при чтении.


Двунаправленная синхронизация

reflect обеспечивает двустороннюю связь между свойством и атрибутом, что делает компонент гибким:

  1. Изменение свойства в JS обновляет DOM:
const el = document.querySelector("my-component");
el.color = "blue"; // атрибут color также становится "blue"
  1. Изменение атрибута в DOM обновляет свойство:
const el = document.querySelector("my-component");
el.setAttribute("color", "green"); // свойство color обновляется на "green"

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


Ограничения и особенности

  • Атрибуты всегда строковые, поэтому типы number и boolean проходят автоматическое преобразование.
  • Сложные объекты и массивы требуют сериализации.
  • reflect не влияет на внутренние вычисляемые свойства или состояния, которые не объявлены через prop.
  • Изменение атрибута через setAttribute инициирует реакцию компонента, что может вызвать перерисовку.

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

Использование reflect особенно удобно в следующих сценариях:

  • Создание настраиваемых элементов, которые должны быть совместимы с HTML-шаблонами.
  • Поддержка реактивных форм и интерфейсов, где состояние компонента должно быть синхронизировано с атрибутами.
  • Легкая интеграция с CSS-псевдоклассами, основанными на атрибутах, например:
my-component[visible] {
    display: block;
}
  • Автоматическая генерация документации и поддержка стандартов веб-компонентов.

Рекомендации по использованию

  • Использовать reflect только для свойств, которые должны быть видимы в DOM.
  • Для внутренних состояний компонента или вычисляемых значений reflect не нужен.
  • При работе с объектами и массивами всегда обеспечивать сериализацию, чтобы избежать ошибок чтения из атрибутов.
  • Проверять совместимость типов, особенно для boolean-свойств, чтобы логика компонента оставалась предсказуемой.

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