Форвардинг рефов

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

Понятие рефов в Haunted

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

Пример создания простого рефа:

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

function MyInput() {
  const inputRef = useRef();

  const focusInput = () => {
    inputRef.current.focus();
  };

  return html`
    <input ${ref(inputRef)} type="text">
    <button @click=${focusInput}>Фокус</button>
  `;
}

customElements.define('my-input', component(MyInput));

Здесь useRef создаёт объект с ключом current, в котором хранится ссылка на <input>.

Проблема прямого доступа

Если родительский компонент хочет напрямую работать с DOM-элементом дочернего компонента, обычный useRef в дочернем компоненте не поможет, так как реф скрыт внутри компонента. Прямой доступ к внутреннему элементу нарушает инкапсуляцию и мешает реактивной модели Haunted.

Форвардинг рефов

Форвардинг рефов решает эту проблему: родительский компонент передаёт реф в дочерний компонент, а дочерний компонент «форвардит» этот реф на нужный внутренний элемент.

Пример форвардинга:

import { html, component, useRef } from 'haunted';
import { ref } from 'lit/directives/ref.js';

function Child({ forwardedRef }) {
  const internalRef = useRef();

  return html`
    <input ${ref(forwardedRef || internalRef)} type="text">
  `;
}

function Parent() {
  const parentRef = useRef();

  const focusChildInput = () => {
    parentRef.current.focus();
  };

  return html`
    <child-component .forwardedRef=${parentRef}></child-component>
    <button @click=${focusChildInput}>Фокус на дочернем input</button>
  `;
}

customElements.define('child-component', component(Child));
customElements.define('parent-component', component(Parent));

Ключевой момент: дочерний компонент использует условное связывание — если forwardedRef передан, он используется, иначе создаётся внутренний реф. Это делает компонент гибким и безопасным для использования как с форвардингом, так и без него.

Важность директивы ref

Haunted строится поверх Lit, поэтому для связывания рефов с DOM-элементами используется директива ref из lit/directives/ref.js. Она обеспечивает корректное присвоение ссылки и обновление при каждом рендере.

Передача методов через реф

Форвардинг рефов позволяет не только получать доступ к DOM, но и передавать методы дочернего компонента родителю.

function Child({ forwardedRef }) {
  const inputRef = useRef();

  const clear = () => {
    inputRef.current.value = '';
  };

  if (forwardedRef) {
    forwardedRef.current = { focus: () => inputRef.current.focus(), clear };
  }

  return html`<input ${ref(inputRef)} type="text">`;
}

function Parent() {
  const childRef = useRef();

  const focusChild = () => childRef.current.focus();
  const clearChild = () => childRef.current.clear();

  return html`
    <child-component .forwardedRef=${childRef}></child-component>
    <button @click=${focusChild}>Фокус</button>
    <button @click=${clearChild}>Очистить</button>
  `;
}

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

Рекомендации по использованию

  • Всегда проверять наличие forwardedRef внутри дочернего компонента, чтобы избежать ошибок при отсутствии передачи.
  • Не присваивать напрямую DOM-элементы, если нужно передавать методы — оборачивать их в объект с методами.
  • Использовать форвардинг только для внешнего управления элементами; внутренние локальные состояния лучше держать внутри дочернего компонента.
  • Поддерживать реактивность: изменения через рефы не должны нарушать работу хуков или перерендер компонента.

Итоговая структура

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