Перейти к содержанию

React

Пакет @lit/react предоставляет утилиты для создания компонентов-оберток React для веб-компонентов и пользовательских хуков из reactive controllers.

Обертка для React-компонентов позволяет задавать свойства пользовательских элементов (вместо атрибутов), сопоставлять события DOM с обратными вызовами в стиле React, а также обеспечивает корректную проверку типов в JSX с помощью TypeScript.

Обертки предназначены для двух разных аудиторий:

  • Пользователи веб-компонентов могут обертывать компоненты и контроллеры для использования в собственных React-проектах.
  • Поставщики компонентов могут публиковать React-обертки, чтобы у пользователей React были идиоматические версии их компонентов.

Зачем нужны обертки?

React уже умеет рендерить веб-компоненты, поскольку пользовательские элементы - это просто HTML-элементы, а React знает, как рендерить HTML. Но React делает некоторые предположения об элементах HTML, которые не всегда справедливы для пользовательских элементов, и обрабатывает имена тегов в нижнем регистре иначе, чем имена компонентов в верхнем регистре, что может сделать использование пользовательских элементов сложнее, чем нужно.

Например, React предполагает, что все JSX-свойства соответствуют атрибутам HTML-элементов, и не предоставляет возможности установить свойства. Это затрудняет передачу сложных данных (например, объектов, массивов или функций) в веб-компоненты. React также предполагает, что все события DOM имеют соответствующие "свойства события" (onclick, onmousemove и т. д.), и использует их вместо вызова addEventListener(). Это означает, что для правильного использования более сложных веб-компонентов вам часто придется использовать ref() и императивный код. (Подробнее об ограничениях интеграции веб-компонентов в React см. в Custom Elements Everywhere).

React работает над исправлением этих проблем, а пока наши обертки позаботятся об установке свойств и прослушивании событий за вас.

Пакет @lit/react предоставляет два основных экспорта:

  • createComponent() создает компонент React, который обертывает существующий веб-компонент. Обертка позволяет вам устанавливать реквизиты компонента и добавлять к нему слушателей событий, как и к любому другому компоненту React.
  • useController() позволяет использовать реактивный контроллер Lit в качестве крючка React.

createComponent

Функция createComponent() создает обертку React-компонента для пользовательского класса элемента. Обертка корректно передает React props свойствам, принимаемым пользовательским элементом, и прослушивает события, отправляемые пользовательским элементом.

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

Импортируйте React, класс пользовательского элемента и createComponent.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import React from 'react';
import { createComponent } from '@lit/react';
import { MyElement } from './my-element.js';

export const MyElementComponent = createComponent({
    tagName: 'my-element',
    elementClass: MyElement,
    react: React,
    events: {
        onactivate: 'activate',
        onchange: 'change',
    },
});

Определив компонент React, вы можете использовать его так же, как и любой другой компонент React.

1
2
3
4
5
<MyElementComponent
    active={isActive}
    onactivate={(e) => setIsActive(e.active)}
    onchange={handleChange}
/>

Посмотрите его в действии в примерах React playground.

Опции

createComponent принимает объект options со следующими свойствами:

  • tagName: Имя тега пользовательского элемента.
  • elementClass: Класс пользовательского элемента.
  • react: Импортированный объект React. Он используется для создания компонента-обертки с предоставленным пользователем React. Это также может быть импорт preact-compat.
  • events: Объект, сопоставляющий реквизит обработчика событий с именем события, вызванного пользовательским элементом.

Использование слотов

Дети компонента, созданного с помощью createComponent(), будут отображаться в слоте по умолчанию пользовательского элемента.

1
2
3
<MyElementComponent>
    <p>This will render in the default slot.</p>
</MyElementComponent>

Чтобы переместить дочерний элемент в определенный слот, можно добавить стандартный атрибут slot.

1
2
3
4
5
<MyElementComponent>
    <p slot="foo">
        This will render in the slot named "foo".
    </p>
</MyElementComponent>

Поскольку компоненты React сами по себе не являются элементами HTML, они обычно не могут напрямую иметь атрибут slot. Для рендеринга в именованный слот компонент должен быть обернут элементом-контейнером с атрибутом slot. Если элемент обертки мешает стилизации, как, например, для макетов grid или flexbox, задайте ему стиль display: contents; (Подробности см. в MDN), чтобы исключить контейнер из рендеринга и рендерить только его дочерние элементы.

1
2
3
4
5
<MyElementComponent>
    <div slot="foo" style="display: contents;">
        <ReactComponent />
    </div>
</MyElementComponent>

Попробуйте это в примере React slots playground.

События

Опция events принимает объект, который сопоставляет имена реквизитов React с именами событий. Когда пользователь компонента передает реквизит callback с одним из имен реквизитов события, обертка добавит его в качестве обработчика соответствующего события.

Хотя имя реквизита React может быть любым, рекомендуемое соглашение - добавлять on перед именем события. Это соответствует тому, как React планирует реализовать поддержку событий для пользовательских элементов. Вы также должны убедиться, что это имя реквизита не пересекается с какими-либо существующими свойствами элемента.

В TypeScript тип события можно указать, приведя имя события к полезному типу EventName. Это хорошая практика для того, чтобы пользователи React получали наиболее точные типы для своих обратных вызовов событий.

Тип EventName - это строка, которая принимает интерфейс события в качестве параметра типа. Здесь мы приводим имя 'my-event' к типу EventName<MyEvent>, чтобы обеспечить правильный тип события:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import React from 'react';
import { createComponent } from '@lit/react';
import { MyElement, type EventName } from './my-element.js';

export const MyElementComponent = createComponent({
    tagName: 'my-element',
    elementClass: MyElement,
    react: React,
    events: {
        'onmy-event': 'my-event' as EventName<MyEvent>,
    },
});

Приведение имени события к EventName<MyEvent> приводит к тому, что у компонента React появляется свойство обратного вызова onMyEvent, принимающее параметр MyEvent вместо простого Event:

1
2
3
4
5
<MyElementComponent
    onmy-event={(e: MyEvent) => {
        console.log(e.myEventData);
    }}
/>

Как это работает

Во время рендеринга обертка получает реквизиты от React и, основываясь на опциях и классе пользовательского элемента, изменяет поведение некоторых реквизитов:

  • Если имя реквизита является свойством пользовательского элемента, как определено с помощью проверки in, обертка устанавливает это свойство элемента в значение реквизита.
  • Если имя реквизита - это имя события, переданное в опцию events, значение реквизита передается в addEventListener() с именем события.
  • В противном случае свойство передается в функцию React createElement() для отображения в качестве атрибута.

Как свойства, так и события добавляются в обратных вызовах componentDidMount() и componentDidUpdate(), поскольку для доступа к элементу он должен быть уже инстанцирован React.

Для событий createComponent() принимает отображение имен реквизитов React событий на события, запускаемые пользовательским элементом. Например, передача {onfoo: 'foo'} означает, что функция, переданная через prop с именем onfoo, будет вызвана, когда пользовательский элемент вызовет событие foo с этим событием в качестве аргумента.

useController

Реактивные контроллеры позволяют разработчикам подключаться к жизненному циклу компонента, чтобы связать воедино состояние и поведение, связанные с функцией. Они похожи на хуки React по пользовательским кейсам и возможностям, но являются обычными объектами JavaScript вместо функций со скрытым состоянием.

useController() позволяет создавать React-хуки из реактивных контроллеров, что обеспечивает совместное использование состояния и поведения веб-компонентов и React.

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react';
import { useController } from '@lit/react/use-controller.js';
import { MouseController } from '@example/mouse-controller';

// Write a custom React hook function:
const useMouse = () => {
    // Use useController to create and store a controller instance:
    const controller = useController(
        React,
        (host) => new MouseController(host),
    );
    // Return relevant data for consumption by the component:
    return controller.pos;
};

// Now use the new hook in a React component:
const Component = (props) => {
    const mousePosition = useMouse();
    return (
        <pre>
            x: {mousePosition.x}
            y: {mousePosition.y}
        </pre>
    );
};

Смотрите пример контроллера мыши в документации по реактивным контроллерам для его реализации.

Как это работает

useController() создает пользовательский хост-объект для переданного ему контроллера и управляет жизненным циклом контроллера с помощью хуков React.

  • useState() используется для хранения экземпляра контроллера и ReactControllerHost.
  • Тело хука и обратные вызовы useLayoutEffect() максимально близко эмулируют жизненный цикл ReactiveElement.
  • ReactControllerHost реализует addController(), чтобы композиция контроллеров работала и жизненные циклы вложенных контроллеров вызывались корректно.
  • ReactControllerHost также реализует requestUpdate(), вызывая сеттер useState(), так что контроллер может вызвать повторный рендеринг своего компонента-хоста.

Комментарии