Композиция хуков

SkateJS — это современная библиотека для создания Web Components на основе стандартов Custom Elements, предоставляющая мощный и гибкий механизм для управления жизненным циклом компонентов через хуки. Хуки в SkateJS представляют собой функции, которые вызываются на определённых этапах жизненного цикла элемента, позволяя добавлять логику без необходимости напрямую вмешиваться в классы компонентов.

Ключевые особенности хуков в SkateJS:

  • Разделение логики и структуры компонента.
  • Повторное использование функциональности между компонентами.
  • Возможность тонкой настройки поведения на разных этапах жизненного цикла.

Жизненный цикл компонентов и точки подключения хуков

Жизненный цикл SkateJS-компонента включает несколько этапов:

  1. Создание элемента (constructor и createdCallback) На этом этапе происходит инициализация состояния и базовой структуры элемента. Хуки здесь позволяют задать начальные значения, установить подписки или подготовить внутренние свойства.

  2. Подключение элемента к DOM (connectedCallback) Вызывается, когда элемент добавляется в DOM. Используется для активации наблюдателей, запуска анимаций, установки слушателей событий и асинхронной загрузки данных.

  3. Отключение элемента от DOM (disconnectedCallback) Хуки на этом этапе позволяют корректно очищать ресурсы: удалять обработчики событий, отменять таймеры, отписываться от подписок.

  4. Обновление (updatedCallback) Вызывается после изменений свойств или атрибутов компонента. Предоставляет возможность оптимизировать перерисовку или реагировать на изменение данных.

Основные хуки SkateJS

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

useState

useState управляет локальным состоянием компонента. Состояние является реактивным: изменения автоматически инициируют обновление DOM.

import { define, useState, html } from 'skatejs';

define('counter-element', {
  render: ({ state }) => html``,
  setup({ state }) {
    state.count = 0;
  },
  connected({ state, element }) {
    element.addEventListener('click', () => state.count++);
  }
});

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

  • Состояние можно инициализировать в setup.
  • Изменения состояния вызывают повторный рендер.
  • Поддержка объектов и массивов с реактивным отслеживанием.

useEffect

useEffect выполняет побочные эффекты после рендера компонента. Аналог useEffect в React, но с учетом особенностей Web Components.

useEffect(() => {
  const interval = setInterval(() => console.log('tick'), 1000);
  return () => clearInterval(interval);
}, []); // пустой массив — эффект выполняется один раз при подключении

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

  • Эффект может возвращать функцию очистки (cleanup), которая выполняется при отключении компонента.
  • Второй аргумент — массив зависимостей, контролирует частоту выполнения.

useRef

useRef позволяет создавать ссылку на DOM-элемент или любую переменную, которая сохраняется между рендерами:

const buttonRef = useRef();
  • Используется для прямого доступа к DOM без нарушения реактивности.
  • Полезно для интеграции с сторонними библиотеками и управления фокусом или размерами элементов.

useMemo и useCallback

useMemo оптимизирует вычисления, которые зависят от определённых значений:

const computedValue = useMemo(() => expensiveComputation(state.count), [state.count]);

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

const handleClick = useCallback(() => state.count++, [state.count]);
  • Оба хука помогают уменьшить количество ненужных вычислений и пересозданий функций, особенно в крупных компонентах.

Композиция хуков

SkateJS позволяет комбинировать несколько хуков в одном компоненте, обеспечивая модульность и переиспользуемость кода. Пример сочетания состояния, эффекта и мемоизации:

define('timer-element', {
  render: ({ state }) => html`
${state.seconds}
`, setup({ state }) { state.seconds = 0; }, connected({ state }) { useEffect(() => { const interval = setInterval(() => state.seconds++, 1000); return () => clearInterval(interval); }, []); } });

Рекомендации по композиции:

  • Разделять эффекты на отдельные хуки для читаемости.
  • Хуки с состоянием и эффектами можно вкладывать друг в друга при необходимости.
  • Стараться избегать циклических зависимостей между хуками.

Практика повторного использования хуков

SkateJS поддерживает создание кастомных хуков, которые инкапсулируют повторяющуюся логику. Пример: хук для управления локальным таймером:

function useTimer(initial = 0) {
  const state = useState({ count: initial });
  useEffect(() => {
    const interval = setInterval(() => state.count++, 1000);
    return () => clearInterval(interval);
  }, []);
  return state;
}

Такой хук можно использовать в нескольких компонентах:

define('countdown-element', {
  render: ({ timer }) => html`${timer.count}`,
  connected({ element }) {
    const timer = useTimer(10);
    element.appendChild(timer.count);
  }
});
  • Кастомные хуки повышают модульность.
  • Позволяют скрыть сложную логику и сделать код компонентов компактным.
  • Обеспечивают единообразное поведение между элементами.

Управление порядком вызова хуков

Порядок вызова хуков имеет значение. SkateJS вызывает их строго в том порядке, в котором они объявлены в компоненте. Нарушение последовательности может привести к некорректной работе состояний и эффектов. Правила:

  • Сначала объявлять useState и useRef.
  • Затем useMemo и useCallback.
  • В конце useEffect для работы с побочными эффектами.

Связь хуков с реактивными свойствами

SkateJS позволяет связывать хуки с реактивными свойствами через объект props:

define('input-element', {
  props: {
    value: String
  },
  connected({ props }) {
    useEffect(() => {
      console.log('Value changed:', props.value);
    }, [props.value]);
  }
});
  • Изменение props автоматически вызывает эффекты с соответствующими зависимостями.
  • Это упрощает синхронизацию состояния компонента с внешними данными.