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

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


Основы создания компонента

В Haunted компонент определяется через функцию и декоратор customElement. Базовая структура выглядит следующим образом:

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

function MyComponent({ title }) {
  return html`<h1>${title}</h1>`;
}

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

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

  • Компонент всегда является функцией.
  • Используется html для шаблонной разметки.
  • component оборачивает функцию для регистрации как веб-компонента.

При портировании существующих компонентов важно учитывать, что методы жизненного цикла классовых компонентов заменяются хуками (useState, useEffect, useRef).


Переход с классовых компонентов

Существующие компоненты на основе классов можно переписать, используя функциональный подход. Рассмотрим пример:

Классовый компонент:

class Counter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;
  }

  connectedCallback() {
    this.render();
  }

  increment() {
    this.count++;
    this.render();
  }

  render() {
    this.innerHTML = `
      <div>
        <span>${this.count}</span>
        <button id="inc">Increment</button>
      </div>
    `;
    this.querySelector('#inc').addEventListener('click', () => this.increment());
  }
}

customElements.define('x-counter', Counter);

Функциональный эквивалент на Haunted:

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

function Counter() {
  const [count, setCount] = useState(0);

  return html`
    <div>
      <span>${count}</span>
      <button @click=${() => setCount(count + 1)}>Increment</button>
    </div>
  `;
}

customElements.define('x-counter', component(Counter));

Особенности портирования:

  • useState заменяет внутренние свойства и управление состоянием.
  • Обработчики событий можно привязывать прямо в шаблоне через @click.
  • Отсутствует необходимость вручную управлять innerHTML и подпиской на события.

Использование хуков

Haunted предоставляет несколько ключевых хуков для управления состоянием и жизненным циклом:

  1. useState(initialValue) — хранение локального состояния.
  2. useEffect(callback, dependencies) — выполнение побочных эффектов при изменении зависимостей.
  3. useRef(initialValue) — хранение ссылок на DOM-элементы или любые данные между рендерами.
  4. useReducer(reducer, initialState) — альтернативный способ управления состоянием через редьюсеры.

Пример использования useEffect при портировании:

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

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(interval);
  }, []);

  return html`<p>Прошло ${seconds} секунд</p>`;
}

customElements.define('x-timer', component(Timer));

Важные детали:

  • Возврат функции из useEffect используется для очистки ресурсов.
  • Пустой массив зависимостей [] имитирует componentDidMount и componentWillUnmount.

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

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

function Greeting({ name = 'Гость' }) {
  return html`<p>Привет, ${name}!</p>`;
}

customElements.define('x-greeting', component(Greeting, { observedAttributes: ['name'] }));

Особенности:

  • observedAttributes позволяет компоненту реагировать на изменения атрибутов.
  • Можно задавать значения по умолчанию через деструктуризацию пропсов.

Организация больших компонентов

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

  1. Хуки состояния для разных частей интерфейса.
  2. Вспомогательные функции для вычислений и генерации шаблонов.
  3. Вложенные компоненты для повторяющихся элементов интерфейса.

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

function TodoItem({ todo, onToggle }) {
  return html`
    <li>
      <input type="checkbox" ?checked=${todo.done} @change=${onToggle}>
      ${todo.text}
    </li>
  `;
}

function TodoList({ todos }) {
  const [items, setItems] = useState(todos);

  const toggleItem = index => {
    const newItems = [...items];
    newItems[index].done = !newItems[index].done;
    setItems(newItems);
  };

  return html`
    <ul>
      ${items.map((todo, i) => html`<${TodoItem} .todo=${todo} .onTog gle=${() => toggleItem(i)} />`)}
    </ul>
  `;
}

customElements.define('x-todo-list', component(TodoList));

Преимущества такой структуры:

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

Интеграция с существующими библиотеками

Haunted поддерживает использование сторонних библиотек, включая работу с DOM напрямую или с state-менеджерами. При портировании важно:

  • Избегать прямого изменения innerHTML вне Haunted.
  • Использовать хуки для синхронизации состояния с внешними источниками.
  • Преобразовать события библиотек в обработчики через привязку в шаблоне.

Советы по оптимизации при портировании

  1. Минимизировать ререндеринг: правильно выбирать зависимости для useEffect и useMemo.
  2. Разделять компоненты на мелкие: каждая функциональная часть интерфейса — отдельный компонент.
  3. Использовать шаблонные литералы Haunted: для динамического HTML и реактивности.
  4. Избегать сложных мутаций DOM: состояние и события должны управляться через хуки.

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