你正在查看 Lit 的旧版本文档。点击 这里查看最新版本。

组件组合

处理复杂度并将 Lit 代码分解为独立单元的最常见方式是 组件组合:即用更小、更简单的组件来构建大型复杂组件的过程。假设你被指派实现一个 UI 页面:

一个显示一组动物照片的应用程序截图。该应用程序有一个顶部栏,包含标题("Fuzzy")和一个菜单按钮。左侧菜单抽屉已打开,显示一组选项。

你可能已经能识别出哪些区域的实现会比较复杂。这些区域很可能就可以作为组件。

通过将复杂度隔离到特定组件中,你可以使工作变得更加简单,然后将这些组件组合在一起创建整体设计。

例如,上面这个相对简单的截图涉及多个可能的组件:顶部栏、菜单按钮、带有用于导航当前部分的菜单项的抽屉;以及主内容区域。每个这些都可以用一个组件来表示。一个复杂组件(如带有导航菜单的抽屉)可以被拆分为多个更小的组件:抽屉本身、用于打开和关闭抽屉的按钮、菜单、以及各个菜单项。

Lit 允许你通过在模板中添加元素来进行组合——无论是内置的 HTML 元素还是自定义元素。

render() {
return html`
<top-bar>
<icon-button icon="menu" slot="nav-button"></icon-button>
<span slot="title">Fuzzy</span>
</top-bar>
`;
}

在决定如何拆分功能时,有几个因素可以帮助你识别何时应该创建新组件。如果一个 UI 部分满足以下一个或多个条件,它可能就是一个好的组件候选:

  • 它有自己的状态。
  • 它有自己的模板。
  • 它在多个地方使用,无论是在同一个组件中还是在多个组件中。
  • 它专注于做好一件事。
  • 它有定义良好的 API。

像按钮、复选框和输入字段这样的可复用控件可以成为很好的组件。但更复杂的 UI 部分(如抽屉和轮播图)也是组件化的好候选。

与子组件交换数据时,一般规则是遵循 DOM 的模型:属性向下事件向上

  • 属性向下。设置子组件的属性通常优于调用子组件的方法。在 Lit 模板和其他声明式模板系统中设置属性很容易。

  • 事件向上。在 Web 平台中,触发事件是元素向上传递信息的默认方法,通常是为了响应用户交互。这使得宿主组件可以响应事件,或者转换或重新触发事件以便更上层的祖先元素接收。

这个模型的一些含义:

  • 组件应该是其 shadow DOM 中子组件的真实数据来源。子组件不应该设置宿主组件的属性或调用其方法。

  • 如果组件更改了自身的公共属性,它应该触发事件来通知树中更高层的组件。通常这些更改是用户操作的结果——比如按下按钮或选择菜单项。可以参考原生 input 元素,它在用户更改输入值时触发事件。

考虑一个菜单组件,它包含一组菜单项并将 itemsselectedItem 属性作为其公共 API 的一部分。它的 DOM 结构可能如下所示:

表示菜单的 DOM 节点层次结构。顶层节点 my-menu 有一个 ShadowRoot,其中包含三个 my-item 元素。

当用户选择一个项目时,my-menu 元素应该更新其 selectedItem 属性。它还应该触发一个事件,通知任何拥有该组件的组件选择已更改。完整的流程大致如下:

  • 用户与一个项目交互,导致事件触发(可以是标准事件如 click,或者是 my-item 组件特有的某个事件)。
  • my-menu 元素获取该事件,并更新其 selectedItem 属性。它可能还会更改某些状态,使被选中的项目高亮显示。
  • my-menu 元素触发一个语义化事件,表示选择已更改。例如,这个事件可以叫做 selected-item-changed。由于这个事件是 my-menu API 的一部分,它在该上下文中应该具有语义意义。

有关派发和监听事件的更多信息,请参阅事件

属性向下和事件向上是一个很好的起点。但如果你需要在两个没有直接后代关系的组件之间交换数据怎么办?例如,在 shadow 树中是兄弟关系的两个组件?

解决这个问题的一种方案是使用 中介者模式。在中介者模式中,对等组件不直接相互通信。相反,交互由第三方_介导_。

实现中介者模式的一种简单方式是让宿主组件处理来自其子组件的事件,然后根据需要将更改后的数据传回树下来更新其子组件的状态。通过添加中介者,你可以使用熟悉的事件向上、属性向下原则来跨树传递数据。

在下面的示例中,中介者元素监听其 shadow DOM 中 input 和 button 元素的事件。它控制按钮的启用状态,使得用户只有在输入框中有文本时才能点击 Submit

其他中介者模式包括 flux/Redux 风格的模式,其中 store 介导更改并通过订阅来更新组件。让组件直接订阅更改可以避免每个父组件都传递其子组件所需的所有数据。

除了 shadow DOM 中的节点外,你还可以渲染组件使用者提供的子节点,就像标准的 <select> 元素可以接受一组 <option> 元素作为子节点并将其渲染为菜单项一样。

子节点有时被称为"light DOM",以区别于组件的 shadow DOM。例如:

<top-bar>
<icon-button icon="menu" slot="nav-button"></icon-button>
<span slot="title">Fuzzy</span>
</top-bar>

这里 top-bar 元素有两个由使用者提供的 light DOM 子节点:一个导航按钮和一个标题。

与 light DOM 子节点的交互不同于与 shadow DOM 中节点的交互。组件 shadow DOM 中的节点由组件管理,不应从组件外部访问。light DOM 子节点由组件外部管理,但也可以被组件访问。组件的使用者可以随时添加或移除 light DOM 子节点,因此组件不能假设有固定的子节点集合。

组件通过其 shadow DOM 中的 <slot> 元素控制子节点是否以及在哪里渲染。它还可以通过监听 slotchange 事件来接收子节点添加和移除的通知。

有关更多信息,请参阅使用 slot 渲染子节点访问已分发的子节点部分。

狐獴照片由 Anggit RizkiantoUnsplash 上拍摄。