Атрибуты и свойства в шаблонах

Haunted — это современная библиотека для создания веб-компонентов на базе Web Components и реактивного подхода, использующая Hooks, аналогичные React. Одним из ключевых аспектов работы с Haunted является корректное управление атрибутами и свойствами компонентов, особенно при построении динамических шаблонов.


Атрибуты vs. свойства

В Web Components и, следовательно, в Haunted существует важное различие между атрибутами (attributes) и свойствами (properties):

  • Атрибуты — это строки, которые определяются в HTML-разметке и доступны через getAttribute/setAttribute. Они сериализуются в HTML, поэтому любые нестроковые значения должны быть преобразованы.

    <my-component title="Пример"></my-component>

    В Jav * aScript:

    const title = element.getAttribute('title'); // "Пример"
  • Свойства — это реальные свойства объекта компонента, которые могут быть любого типа (объекты, массивы, функции). Они не сериализуются автоматически в HTML.

    element.data = { id: 1, name: 'Тест' };

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


Декларация свойств в Haunted

Haunted позволяет реактивно отслеживать изменения свойств компонента через механизм @property (через Lit-like API) или с использованием хуков. Для обычных функциональных компонентов управление свойствами происходит через параметры функции:

import { html, component } from 'haunted';

function MyComponent({ title, count }) {
  return html`
    <h1>${title}</h1>
    <p>Количество: ${count}</p>
  `;
}

customElements.define('my-component', component(MyComponent));

В этом примере title и count автоматически становятся свойствами компонента, которые можно передавать из HTML или JavaScript.


Привязка атрибутов в шаблоне

Haunted использует синтаксис lit-html, поэтому для вставки динамических значений в атрибуты применяются ${}. Важно помнить, что это работает по-разному для строковых атрибутов и свойств объекта:

html`<input type="text" .value=${value} placeholder=${placeholder} />`
  • placeholder=${placeholder} — устанавливает атрибут placeholder, который отображается в HTML.
  • .value=${value} — устанавливает свойство value объекта input, что критично для реактивного обновления значения поля.

Префикс . указывает Haunted (и Lit) на то, что нужно работать с свойством элемента, а не с атрибутом.


Реактивное обновление

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

function Counter({ count }) {
  return html`<p>Счетчик: ${count}</p>`;
}

const counterElement = document.createElement('my-counter');
counterElement.count = 10;
document.body.appendChild(counterElement);

setTimeout(() => {
  counterElement.count = 15; // Автоматический ререндер
}, 1000);

Если необходимо обновлять атрибуты напрямую, лучше использовать reflect в сочетании с кастомным элементом для синхронизации свойства и атрибута:

class ReflectComponent extends HTMLElement {
  static get observedAttributes() { return ['name']; }

  get name() { return this._name; }
  set name(val) {
    this._name = val;
    this.setAttribute('name', val); // отражение свойства в атрибут
  }

  attributeChangedCallback(attr, oldVal, newVal) {
    if (attr === 'name') this._name = newVal;
  }
}

Управление boolean-атрибутами

Boolean-атрибуты требуют отдельного подхода, так как их присутствие в HTML означает true, а отсутствие — false. В Haunted рекомендуется использовать привязку к свойству:

html`<button ?disabled=${isDisabled}>Нажми меня</button>`
  • ?disabled=${isDisabled} — синтаксис для boolean-атрибутов. Если isDisabled истинно, атрибут disabled добавляется, если ложь — удаляется.

Динамические свойства объектов

Свойства сложных типов (объекты, массивы) передаются как свойства компонента, а не атрибуты. Атрибуты поддерживают только строковые значения. Пример реактивного объекта:

function UserCard({ user }) {
  return html`
    <div>
      <h2>${user.name}</h2>
      <p>Email: ${user.email}</p>
    </div>
  `;
}

const card = document.createElement('user-card');
card.user = { name: 'Иван', email: 'ivan@mail.com' };
document.body.appendChild(card);

Любое обновление объекта user через присваивание нового значения вызовет повторный рендер.


Совместимость с событиями

При работе со свойствами часто требуется синхронизация с событиями DOM:

html`<input .value=${value} @input=${e => value = e.target.value} />`
  • .value=${value} — связывает свойство input с переменной value.
  • @input=${...} — обрабатывает событие изменения. Это позволяет организовать двустороннюю привязку данных.

Резюме по лучшим практикам

  • Использовать атрибуты для простых строк и для HTML-семантики.
  • Использовать свойства для реактивных и сложных типов данных.
  • Для boolean-атрибутов применять синтаксис ?attr=${value}.
  • Префикс . в шаблонах гарантирует установку свойства DOM-элемента.
  • Реактивное обновление свойств — основной инструмент динамических компонентов Haunted.

Корректное управление атрибутами и свойствами обеспечивает не только реактивность, но и правильную интеграцию компонентов с другими элементами страницы и нативными API браузера.