组件组合

处理复杂性并将 Lit 代码分解为独立单元的最常见方法是组件组合:即将一个大型、复杂的组件构建成多个更小、更简单的组件的过程。想象你被分配实现一个 UI 界面:

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

你可能已经识别出了实现起来会涉及一些复杂性的区域。很可能,这些都可以成为组件。

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

例如,上面这个相当简单的截图涉及多个可能的组件:一个顶部栏、一个菜单按钮、一个带有导航菜单项的抽屉,以及一个主要内容区域。每个这些都可以由一个组件表示。像带有导航菜单的抽屉这样的复杂组件,可能会被分解成许多更小的组件:抽屉本身、用于打开和关闭抽屉的按钮、菜单、单个菜单项。

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

在决定如何拆分功能时,有几个方面可以帮助确定何时创建新组件。如果一个 UI 部分符合以下一个或多个条件,它可能是一个好的组件候选:

  • 它有自己的状态。
  • 它有自己的模板。
  • 它在这个组件中或多个组件中被多次使用。
  • 它专注于把一件事做好。
  • 它有一个定义良好的 API。

可重用的控件如按钮、复选框和输入字段都可以成为很好的组件。但更复杂的 UI 部分如抽屉和轮播也是组件化的绝佳候选。

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

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

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

这个模型的一些含义:

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

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

考虑一个包含一组菜单项并在其公共 API 中暴露 itemsselectedItem 属性的菜单组件。它的 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 中输入和按钮元素的事件。它控制按钮的启用状态,这样用户只有在输入框中有文本时才能点击提交

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

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

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

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

与 light DOM 子元素的交互与与 shadow DOM 中的节点的交互不同。组件的 shadow DOM 中的节点由组件管理,不应该从组件外部访问。light DOM 子元素从组件外部管理,但组件也可以访问它们。组件的用户可以随时添加或删除 light DOM 子元素,所以组件不能假设有一个静态的子节点集。

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

更多信息,请参见使用插槽渲染子元素访问插槽的子元素部分。

狐獴照片由 Anggit RizkiantoUnsplash 上拍摄。