React

@lit/react 包提供了实用程序,用于为 Web 组件创建 React 包装器组件,以及来自 响应式控制器 的自定义钩子。

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 组件一样。

  • useController() 允许您将 Lit 响应式控制器用作 React 钩子。

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

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

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

React 游乐场示例 中查看实际效果。

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

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

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

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

由于 React 组件本身不是 HTML 元素,因此它们通常不能直接具有 slot 属性。要渲染到命名插槽,组件需要用具有 slot 属性的容器元素包装。如果包装器元素会干扰样式,例如网格和 flexbox 布局,则为其提供 display: contents; 样式(有关详细信息,请参阅 MDN)将从渲染中删除容器,并且只渲染其子元素。

React 插槽游乐场示例 中试用一下。

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

虽然 React 道具名称可以是您想要的任何名称,但推荐的约定是在事件名称前面添加 on。这与 React 计划如何为自定义元素实现事件支持相匹配。您还应该确保此道具名称不会与元素上的任何现有属性发生冲突。

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

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

将事件名称强制转换为 EventName<MyEvent> 将导致 React 组件具有一个 onMyEvent 回调道具,该道具接受一个 MyEvent 参数,而不是一个普通的 Event

在渲染期间,包装器接收来自 React 的道具,并根据选项和自定义元素类,更改某些道具的行为

  • 如果道具名称是自定义元素上的属性,如 in 检查所确定,则包装器将元素上的该属性设置为道具值
  • 如果道具名称是传递给 events 选项的事件名称,则道具值将与事件名称一起传递给 addEventListener()
  • 否则,道具将传递给 React 的 createElement() 以将其渲染为属性。

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

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

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

useController() 允许您将响应式控制器变成 React 钩子,从而允许跨 Web 组件和 React 共享状态和行为。

在响应式控制器文档中查看 鼠标控制器示例,了解其实现。

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

  • useState() 用于存储控制器的实例和 ReactControllerHost
  • 钩子主体和 useLayoutEffect() 回调尽可能地模拟 ReactiveElement 生命周期。
  • ReactControllerHost 实现 addController(),以便控制器组合正常工作,并且嵌套控制器生命周期被正确调用。
  • ReactControllerHost 还通过调用 useState() 设置器来实现 requestUpdate(),以便控制器可以使其主机组件重新渲染。