React

@lit/react 包提供了用于为 web 组件创建 React 包装器组件,以及从 响应式控制器 创建自定义 hooks 的实用工具。

React 组件包装器使你能够在自定义元素上设置属性(而不仅仅是特性),将 DOM 事件映射到 React 风格的回调,并使 TypeScript 能够在 JSX 中进行正确的类型检查。

这些包装器针对两类不同的受众:

  • web 组件的使用者可以为他们自己的 React 项目包装组件和控制器。
  • 组件供应商可以发布 React 包装器,这样他们的 React 用户就能使用符合习惯的组件版本。

React 已经可以渲染 web 组件,因为自定义元素就是 HTML 元素,而 React 知道如何渲染 HTML。但是 React 对 HTML 元素做出了一些假设,这些假设并不总是适用于自定义元素,并且它对小写标签名和大写组件名的处理方式不同,这可能会使自定义元素的使用比必要的更困难。

例如,React 假设所有 JSX 属性都映射到 HTML 元素特性,并且不提供设置属性的方法。这使得向 web 组件传递复杂数据(如对象、数组或函数)变得困难。React 还假设所有 DOM 事件都有对应的"事件属性"(onclickonmousemove 等),并使用这些而不是调用 addEventListener()。这意味着要正确使用更复杂的 web 组件,你经常需要使用 ref() 和命令式代码。(有关 React web 组件集成限制的更多信息,请参见 Custom Elements Everywhere。)

React 正在修复这些问题,但在此期间,我们的包装器会帮你处理属性设置和事件监听。

@lit/react 包提供了两个主要导出:

  • createComponent() 创建一个 React 组件来_包装_现有的 web 组件。包装器允许你像对待任何其他 React 组件一样在组件上设置 props 和添加事件监听器。

  • useController() 让你可以将 Lit 响应式控制器用作 React hook。

createComponent() 函数为自定义元素类创建一个 React 组件包装器。包装器正确地将 React props 传递给自定义元素接受的属性,并监听自定义元素分发的事件。

导入 React、自定义元素类和 createComponent

定义 React 组件后,你可以像使用任何其他 React 组件一样使用它。

React playground 示例中查看实际效果。

createComponent 接受一个具有以下属性的选项对象:

  • tagName:自定义元素的标签名。
  • elementClass:自定义元素类。
  • react:导入的 React 对象。这用于使用用户提供的 React 创建包装器组件。这也可以是 preact-compat 的导入。
  • events:一个对象,将事件处理器 prop 映射到自定义元素触发的事件名称。

使用 createComponent() 创建的组件的子元素将渲染到自定义元素的默认插槽中。

要将子元素渲染到特定的命名插槽中,可以添加标准的 slot 特性。

由于 React 组件本身不是 HTML 元素,它们通常不能直接拥有 slot 特性。要渲染到命名插槽中,组件需要用具有 slot 特性的容器元素包装。如果包装元素干扰了样式,比如在网格和弹性盒布局中,给它一个 display: contents; 样式(详见 MDN)将移除容器的渲染,只渲染其子元素。

React slots playground 示例中试试看。

events 选项接受一个将 React prop 名称映射到事件名称的对象。当组件用户传递一个带有事件 prop 名称的回调 prop 时,包装器会将其作为相应事件的事件处理器添加。

虽然 React prop 名称可以是任何你想要的,但建议的约定是在事件名称前添加 on。这与 React 计划实现的自定义元素事件支持相匹配。你还应该确保这个 prop 名称不会与元素上的任何现有属性冲突。

在 TypeScript 中,可以通过将事件名称转换为 EventName 实用类型来指定事件类型。这是一个很好的做法,这样 React 用户就能获得最准确的事件回调类型。

EventName 类型是一个字符串,它接受一个事件接口作为类型参数。这里我们将 'my-event' 名称转换为 EventName<MyEvent> 以提供正确的事件类型:

将事件名称转换为 EventName<MyEvent> 会导致 React 组件有一个接受 MyEvent 参数而不是普通 EventonMyEvent 回调 prop:

在渲染期间,包装器从 React 接收 props,并根据选项和自定义元素类,改变一些 props 的行为:

  • 如果 prop 名称是自定义元素上的属性(通过 in 检查确定),包装器将该属性设置为 prop 值
  • 如果 prop 名称是传递给 events 选项的事件名称,prop 值将被传递给 addEventListener() 和事件名称。
  • 否则,prop 将被传递给 React 的 createElement() 以作为特性渲染。

属性和事件都在 componentDidMount()componentDidUpdate() 回调中添加,因为必须先由 React 实例化元素才能访问它。

对于事件,createComponent() 接受一个从 React 事件 prop 名称到自定义元素触发的事件的映射。例如,传递 {onfoo: 'foo'} 意味着通过名为 onfoo 的 prop 传递的函数将在自定义元素触发 foo 事件时被调用,事件作为参数。

响应式控制器允许开发者挂钩到组件的生命周期,将与功能相关的状态和行为捆绑在一起。它们在用户案例和功能上类似于 React hooks,但是是普通的 JavaScript 对象而不是带有隐藏状态的函数。

useController() 让你可以将响应式控制器制作成 React hooks,允许在 web 组件和 React 之间共享状态和行为。

有关其实现,请参见响应式控制器文档中的 鼠标控制器示例

useController() 为传递给它的控制器创建一个自定义主机对象,并通过使用 React hooks 驱动控制器的生命周期。

  • useState() 用于存储控制器实例和 ReactControllerHost
  • hook 主体和 useLayoutEffect() 回调尽可能地模拟 ReactiveElement 生命周期。
  • ReactControllerHost 实现了 addController(),因此控制器组合可以工作,嵌套控制器生命周期被正确调用。
  • ReactControllerHost 还通过调用 useState() setter 实现了 requestUpdate(),因此控制器可以使其主机组件重新渲染。