Отражение свойств в атрибуты

Haunted — это библиотека для создания веб-компонентов с использованием реактивных хуков, вдохновлённых React, но работающих на нативных Custom Elements. Одной из ключевых особенностей является возможность автоматического связывания свойств компонента с HTML-атрибутами, что позволяет управлять состоянием через разметку и получать реактивное обновление при изменениях.

Свойства и атрибуты

В Haunted свойство компонента — это обычное поле объекта, которое хранит данные компонента. Атрибуты — это значения, передаваемые через HTML. Связь между ними реализуется через reflection, то есть автоматическое преобразование значения свойства в атрибут и обратно.

Пример объявления свойства с отражением:

import { component, html } from 'haunted';
import { useState } from 'haunted';

function Counter({ count = 0 }) {
  const [value, setValue] = useState(count);

  return html`
    <div>
      <p>Счёт: ${value}</p>
      <button @click=${() => setValue(value + 1)}>Увеличить</button>
    </div>
  `;
}

customElements.define('my-counter', component(Counter, { observedAttributes: ['count'] }));

В этом примере count — свойство, которое отражается на атрибут. Если в HTML прописать <my-counter count="5"></my-counter>, компонент инициализируется с начальным значением 5. Любые изменения свойства внутри компонента можно синхронизировать с атрибутом через ручное присваивание this.setAttribute('count', value) или через соответствующие хуки.

Типы данных и их преобразование

При отражении свойств в атрибуты важно учитывать, что HTML-атрибуты всегда строки. Haunted предоставляет возможности для преобразования типов:

  • Boolean: присутствие атрибута считается true, отсутствие — false.
  • Number: строковое значение автоматически преобразуется в число через Number().
  • Object / Array: можно сериализовать в JSON, чтобы хранить сложные структуры в атрибуте.

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

function Toggle({ active = false }) {
  const [state, setState] = useState(active);

  return html`
    <button 
      class=${state ? 'active' : ''}
      @click=${() => setState(!state)}>
      ${state ? 'Вкл' : 'Выкл'}
    </button>
  `;
}

customElements.define(
  'my-toggle', 
  component(Toggle, { observedAttributes: ['active'] })
);

Если в HTML добавить <my-toggle active></my-toggle>, кнопка будет активной. При изменении свойства state вручную можно синхронизировать атрибут с помощью this.toggleAttribute('active', state).

Двусторонняя связь

Haunted поддерживает реактивное обновление при изменении атрибутов через механизм observedAttributes. Для этого необходимо:

  1. Перечислить атрибуты в массиве observedAttributes.
  2. Реализовать метод attributeChangedCallback при необходимости кастомного поведения.
class MyElement extends HTMLElement {
  static get observedAttributes() {
    return ['value'];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'value') {
      this.value = Number(newValue);
    }
  }
}

При использовании Haunted этот шаг упрощается, так как библиотека автоматически обновляет свойства, если они отражаются на атрибуты.

Связывание через хук useRef

Для более сложного взаимодействия можно использовать useRef для прямого доступа к DOM и синхронизации состояния с атрибутами:

import { component, html } from 'haunted';
import { useState, useRef, useEffect } from 'haunted';

function InputMirror({ text = '' }) {
  const [value, setValue] = useState(text);
  const inputRef = useRef();

  useEffect(() => {
    inputRef.current.value = value;
    inputRef.current.setAttribute('text', value);
  }, [value]);

  return html`<input type="text" ref=${inputRef} @input=${e => setValue(e.target.value)} />`;
}

customElements.define('input-mirror', component(InputMirror, { observedAttributes: ['text'] }));

Здесь любое изменение состояния value автоматически отражается на атрибуте text.

Ограничения и лучшие практики

  • Не все типы данных удобно отражать в атрибуты. Для объектов и массивов лучше использовать сериализацию.
  • Частые обновления атрибутов могут снижать производительность, так как это вызывает перераисовку компонента.
  • Для булевых флагов предпочтительно использовать наличие/отсутствие атрибута, а не строки "true"/"false".
  • Всегда синхронизировать типы при чтении атрибута, чтобы избежать ошибок при парсинге.

Итоговые возможности отражения

  • Автоматическая синхронизация свойств и атрибутов.
  • Поддержка разных типов данных с возможностью кастомного преобразования.
  • Реактивное обновление при изменении атрибутов через HTML.
  • Возможность интеграции с нативными DOM-методами через useRef и хуки.

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