React
@lit/react 包提供了用于为 web 组件创建 React 包装器组件,以及从 响应式控制器 创建自定义 hooks 的实用工具。
React 组件包装器使你能够在自定义元素上设置属性(而不仅仅是特性),将 DOM 事件映射到 React 风格的回调,并使 TypeScript 能够在 JSX 中进行正确的类型检查。
这些包装器针对两类不同的受众:
- web 组件的使用者可以为他们自己的 React 项目包装组件和控制器。
- 组件供应商可以发布 React 包装器,这样他们的 React 用户就能使用符合习惯的组件版本。
为什么需要包装器?
Permalink to "为什么需要包装器?"React 已经可以渲染 web 组件,因为自定义元素就是 HTML 元素,而 React 知道如何渲染 HTML。但是 React 对 HTML 元素做出了一些假设,这些假设并不总是适用于自定义元素,并且它对小写标签名和大写组件名的处理方式不同,这可能会使自定义元素的使用比必要的更困难。
例如,React 假设所有 JSX 属性都映射到 HTML 元素特性,并且不提供设置属性的方法。这使得向 web 组件传递复杂数据(如对象、数组或函数)变得困难。React 还假设所有 DOM 事件都有对应的"事件属性"(onclick、onmousemove 等),并使用这些而不是调用 addEventListener()。这意味着要正确使用更复杂的 web 组件,你经常需要使用 ref() 和命令式代码。(有关 React web 组件集成限制的更多信息,请参见 Custom Elements Everywhere。)
React 正在修复这些问题,但在此期间,我们的包装器会帮你处理属性设置和事件监听。
@lit/react 包提供了两个主要导出:
createComponent()创建一个 React 组件来_包装_现有的 web 组件。包装器允许你像对待任何其他 React 组件一样在组件上设置 props 和添加事件监听器。useController()让你可以将 Lit 响应式控制器用作 React hook。
createComponent
Permalink to "createComponent"createComponent() 函数为自定义元素类创建一个 React 组件包装器。包装器正确地将 React props 传递给自定义元素接受的属性,并监听自定义元素分发的事件。
导入 React、自定义元素类和 createComponent。
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 组件一样使用它。
<MyElementComponent active={isActive} onactivate={(e) => setIsActive(e.active)} onchange={handleChange}/>在 React playground 示例中查看实际效果。
createComponent 接受一个具有以下属性的选项对象:
tagName:自定义元素的标签名。elementClass:自定义元素类。react:导入的React对象。这用于使用用户提供的React创建包装器组件。这也可以是preact-compat的导入。events:一个对象,将事件处理器 prop 映射到自定义元素触发的事件名称。
使用 createComponent() 创建的组件的子元素将渲染到自定义元素的默认插槽中。
<MyElementComponent> <p>这将渲染在默认插槽中。</p></MyElementComponent>要将子元素渲染到特定的命名插槽中,可以添加标准的 slot 特性。
<MyElementComponent> <p slot="foo">这将渲染在名为 "foo" 的插槽中。</p></MyElementComponent>由于 React 组件本身不是 HTML 元素,它们通常不能直接拥有 slot 特性。要渲染到命名插槽中,组件需要用具有 slot 特性的容器元素包装。如果包装元素干扰了样式,比如在网格和弹性盒布局中,给它一个 display: contents; 样式(详见 MDN)将移除容器的渲染,只渲染其子元素。
<MyElementComponent> <div slot="foo" style="display: contents;"> <ReactComponent /> </div></MyElementComponent>在 React slots playground 示例中试试看。
events 选项接受一个将 React prop 名称映射到事件名称的对象。当组件用户传递一个带有事件 prop 名称的回调 prop 时,包装器会将其作为相应事件的事件处理器添加。
虽然 React prop 名称可以是任何你想要的,但建议的约定是在事件名称前添加 on。这与 React 计划实现的自定义元素事件支持相匹配。你还应该确保这个 prop 名称不会与元素上的任何现有属性冲突。
在 TypeScript 中,可以通过将事件名称转换为 EventName 实用类型来指定事件类型。这是一个很好的做法,这样 React 用户就能获得最准确的事件回调类型。
EventName 类型是一个字符串,它接受一个事件接口作为类型参数。这里我们将 'my-event' 名称转换为 EventName<MyEvent> 以提供正确的事件类型:
import React from 'react';import {createComponent, type EventName} from '@lit/react';import {MyElement, MyEvent} 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 组件有一个接受 MyEvent 参数而不是普通 Event 的 onMyEvent 回调 prop:
<MyElementComponent onmy-event={(e: MyEvent) => { console.log(e.myEventData); }}/>在渲染期间,包装器从 React 接收 props,并根据选项和自定义元素类,改变一些 props 的行为:
- 如果 prop 名称是自定义元素上的属性(通过
in检查确定),包装器将该属性设置为 prop 值 - 如果 prop 名称是传递给
events选项的事件名称,prop 值将被传递给addEventListener()和事件名称。 - 否则,prop 将被传递给 React 的
createElement()以作为特性渲染。
属性和事件都在 componentDidMount() 和 componentDidUpdate() 回调中添加,因为必须先由 React 实例化元素才能访问它。
对于事件,createComponent() 接受一个从 React 事件 prop 名称到自定义元素触发的事件的映射。例如,传递 {onfoo: 'foo'} 意味着通过名为 onfoo 的 prop 传递的函数将在自定义元素触发 foo 事件时被调用,事件作为参数。
useController
Permalink to "useController"响应式控制器允许开发者挂钩到组件的生命周期,将与功能相关的状态和行为捆绑在一起。它们在用户案例和功能上类似于 React hooks,但是是普通的 JavaScript 对象而不是带有隐藏状态的函数。
useController() 让你可以将响应式控制器制作成 React hooks,允许在 web 组件和 React 之间共享状态和行为。
import React from 'react';import {useController} from '@lit/react/use-controller.js';import {MouseController} from '@example/mouse-controller';
// 编写自定义 React hook 函数:const useMouse = () => { // 使用 useController 创建并存储控制器实例: const controller = useController(React, (host) => new MouseController(host)); // 返回相关数据供组件使用: return controller.pos;};
// 现在在 React 组件中使用新的 hook:const Component = (props) => { const mousePosition = useMouse(); return ( <pre> x: {mousePosition.x} y: {mousePosition.y} </pre> );};有关其实现,请参见响应式控制器文档中的 鼠标控制器示例。
useController() 为传递给它的控制器创建一个自定义主机对象,并通过使用 React hooks 驱动控制器的生命周期。
useState()用于存储控制器实例和ReactControllerHost- hook 主体和
useLayoutEffect()回调尽可能地模拟ReactiveElement生命周期。 ReactControllerHost实现了addController(),因此控制器组合可以工作,嵌套控制器生命周期被正确调用。ReactControllerHost还通过调用useState()setter 实现了requestUpdate(),因此控制器可以使其主机组件重新渲染。