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()
,因此控制器可以使其主机组件重新渲染。