Higher-Order Components

Higher-Order Components (HOC) в SkateJS представляют собой паттерн для повторного использования логики компонентов, позволяющий создавать новые компоненты на основе существующих. В отличие от обычных компонентов, HOC не изменяют исходный компонент напрямую, а возвращают расширенную версию с дополнительными возможностями.


Принцип работы HOC

HOC в SkateJS реализуются как функции, принимающие компонент и возвращающие новый компонент. Важно понимать, что HOC не должны рендерить JSX напрямую; их задача — инъекция логики, свойств или стилей в базовый компонент.

Пример базового синтаксиса HOC:

import { define, Component, h } from 'skatejs';

function withLogging(BaseComponent) {
  return class extends BaseComponent {
    connectedCallback() {
      super.connectedCallback && super.connectedCallback();
      console.log(`${BaseComponent.is} подключён к DOM`);
    }
  };
}

class MyButton extends Component {
  static get is() { return 'my-button'; }

  renderCallback() {
    return <button>Нажми меня</button>;
  }
}

define(MyButton);

const LoggedButton = withLogging(MyButton);
define(LoggedButton);

В этом примере withLogging добавляет функциональность логирования, не вмешиваясь в исходный компонент MyButton.


Модификация свойств через HOC

HOC можно использовать для добавления или изменения свойств компонента. SkateJS предоставляет API для декларативного описания свойств через static get props().

Пример расширения компонента с новым свойством:

function withCounter(BaseComponent) {
  return class extends BaseComponent {
    static get props() {
      return {
        ...BaseComponent.props,
        count: { attribute: true, default: 0 }
      };
    }

    increment() {
      this.count++;
    }

    renderCallback() {
      return (
        <div>
          {super.renderCallback()}
          <span>Счётчик: {this.count}</span>
          <button oncl ick={() => this.increment()}>+</button>
        </div>
      );
    }
  };
}

class SimpleBox extends Component {
  static get is() { return 'simple-box'; }

  renderCallback() {
    return <div>Контент блока</div>;
  }
}

define(SimpleBox);

const CounterBox = withCounter(SimpleBox);
define(CounterBox);

Здесь HOC withCounter добавляет свойство count и методы для его изменения, при этом исходный компонент остаётся неизменным.


Комбинирование нескольких HOC

SkateJS позволяет цепочку HOC, создавая компонент с множественными расширениями. Порядок применения имеет значение: первый HOC оборачивает компонент, второй — уже результат работы первого.

const EnhancedButton = withLogging(withCounter(MyButton));
define(EnhancedButton);

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


HOC и жизненный цикл компонентов

HOC могут взаимодействовать с методами жизненного цикла SkateJS:

  • connectedCallback()
  • disconnectedCallback()
  • updated()
  • renderCallback()

При использовании HOC необходимо всегда вызывать super, чтобы сохранить оригинальное поведение базового компонента.

function withTimer(BaseComponent) {
  return class extends BaseComponent {
    connectedCallback() {
      super.connectedCallback && super.connectedCallback();
      this._timer = setInterval(() => console.log('Тик'), 1000);
    }

    disconnectedCallback() {
      clearInterval(this._timer);
      super.disconnectedCallback && super.disconnectedCallback();
    }
  };
}

Это гарантирует корректное взаимодействие с жизненным циклом и предотвращает утечки ресурсов.


HOC и Shadow DOM

SkateJS поддерживает Shadow DOM по умолчанию. HOC может добавлять содержимое в теневой корень компонента, расширяя его визуальную структуру:

function withWrapper(BaseComponent) {
  return class extends BaseComponent {
    renderCallback() {
      return (
        <div class="wrapper">
          {super.renderCallback()}
        </div>
      );
    }
  };
}

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


Практические рекомендации при работе с HOC в SkateJS

  1. Избегать мутаций исходного компонента. HOC должны возвращать новый класс, а не изменять существующий.
  2. Минимизировать влияние на рендер. Не стоит перегружать renderCallback сложной логикой, лучше добавлять свойства и обработчики.
  3. Явно вызывать super. Любое расширение методов жизненного цикла должно сохранять поведение базового компонента.
  4. Использовать композицию. Несколько HOC можно объединять для постепенного наращивания функциональности.

Применение HOC для повторного использования логики

HOC в SkateJS идеально подходят для следующих задач:

  • Добавление логирования, аналитики или таймеров
  • Расширение свойств и методов компонентов
  • Внедрение повторяющихся UI-структур (обёртки, стили)
  • Реализация кросс-компонентной логики, не затрагивая базовые элементы

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