为不同环境创建 Web Components

Avatar of Mattia Astorino
Mattia Astorino

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 价值 200 美元的免费信用额度!

本文不讲解如何构建 Web Components。 Caleb Williams 已经为此写了一篇 全面指南。我们来谈谈如何使用它们,构建它们时需要考虑哪些因素,以及如何在项目中使用它们。

如果您不熟悉 Web Components,Caleb 的指南非常值得一读,但以下是一些可以帮助您快速上手的资源:

由于 Web Components 现在已得到 广泛支持(感谢幕后众多人的努力)——并且考虑到 Edge 将 即将切换 到 chromium 平台——现在人们开始将 Web Components 视为“原生”且符合平台的构建可重用 UI 组件的方式,以保持设计系统和 Web 项目之间的一致性,同时利用 Shadow DOM 的强大功能在组件本身内部封装样式和逻辑。

嗯,这在某种程度上既正确又错误。但首先,让我们先了解一下 **抽象层三角形**。

抽象层三角形

从技术角度讲,我们应该将 Web Components 视为我们最喜欢的标记语言 HTML 的扩展(没错!)。 Web Components API 允许我们创建 HTML 中不存在的自定义 HTML 元素(例如 <foo-bar>)。

我们被告知 Web Components 基本上是新的 HTML 元素,因此我们应该将它们视为 HTML 规范的一部分,因此,我们应该遵循其范式、核心概念和使用方式。如果我们假设所有这些要点,我们将发现我们的组件将存在于 Web 平台堆栈的最低级别,与 HTML、CSS 和 JavaScript 并列。

像 React、Vue、Angular、SvelteJS 这样的框架和库在它们自己的抽象级别上运行,位于其他工具之上,这些工具存在于一种“中土世界”中,比如 StencilJs、Hybrids 和 Lit。在这些抽象层之下,我们可以找到我们的基本 Web 技术……以及原生 Web Components。我们可以使用 **ALT**(**A**bstraction **L**ayer **T**riangle)图表来表示这个概念

我们越高,抽象程度就越高。

这为什么很重要呢?嗯,它可以帮助我们可视化原生组件之上存在的各个层,并了解它们的使用环境,以便可以为目标环境构建它们。什么是环境?这就是我们要探讨的内容。

相同的技术,不同的环境

Shadow DOM 是 Web Components API 中的关键因素。它允许我们将 JavaScript 和 CSS 捆绑到自定义元素中,以防止外部干扰和样式操作,除非我们明确允许。实际上,开发人员可以采取一些方法来允许外部 CSS 泄漏到 shadow root 和组件中,包括自定义属性以及 ::part::theme 伪元素,这些方法是 Monica Dinculescu) 对 进行了很好的阐述

还需要考虑另一件事:我们正在使用的环境。经过三年亲自构建 Web Components,我确定了两种环境:**私有** 环境(如设计系统)和 **标准** 环境(如没有自定义样式的普通 HTML、CSS 和 JavaScript)。

在设计组件之前,我们需要了解它们的用途,因此确定环境类型是所有这些的关键。最简单的方法是仅针对一个环境,但只要使用一个小技巧,我们就可以为两种 环境构建组件。

在我们深入了解之前,让我们先看看这两种环境之间的区别。

私有环境

**私有环境** 是一个封闭的生态系统,它为组件提供了自己的样式,这些样式必须按原样使用。因此,如果我们正在构建一个来自特定样式指南或设计系统的组件库,每个组件都会反映自定义样式,因此无需每次需要时都对它们进行编码。

JavaScript 逻辑也是如此。例如,我们可以附加一个封闭的 shadow root,防止其他人使用 querySelector 意外地穿透 shadow boundary。因此,我们可以简单地选择和使用任何组件,避免样式不一致和 CSS 冲突等问题。作为作者,您还可以定义一组 CSS 自定义属性(或 ::parts),这些属性可用于针对特定用例对组件进行样式设置,但这不是设计系统的重点。

这是一个私有环境中 Web Component 组件的示例。它所有的样式和逻辑都包含在 shadow-root 中,可以简单地将其放入任何页面中。

查看 CodePen 上 Mattia Astorino (@equinusocio) 的笔
封闭环境 Web Component

CodePen 上。

这个示例和接下来的示例仅用于演示目的,并非用于生产环境,因为它们没有考虑关键情况,例如可访问性和其他优化。

私有环境中的组件很少在该环境之外使用。例如,如果我们尝试从设计系统(具有自己的强制样式)中获取一个元素,我们无法简单地将其添加到项目中并期望对其进行自定义。您知道 Bootstrap 如何可以根据您的喜好进行主题设置和自定义吗?这几乎与之相反。这些组件旨在在其环境中生存,并且只在其环境中生存。

标准环境

**标准** 环境可能是最复杂的组件类型,不仅因为环境可以从完整的框架(如 Vue 和 React)到普通的原生 HTML 不等,而且因为每个人都应该能够像使用其他元素一样使用该组件。

例如,当公开添加一个组件时,比如添加到 npm,那些使用它的人会期望能够对其进行自定义,至少在一定程度上是可以的。

您知道哪个 HTML 元素带有自己的表示性样式吗?答案应该是没有,因为,嗯,元素必须使用 CSS 显式设置样式。对于标准环境中创建的 Web Components 也是如此。单个 Web Component 应该可以通过添加类和属性或其他方法进行自定义。

这是我们在封闭环境示例中看到的同一个 <todo-list> 元素,但它是为标准环境设计的。它按原样工作,其 shadow root 中没有任何表示性样式。实际上,它只包含必要的逻辑和基本 CSS,以确保它能够正常工作。否则,它完全可以像任何标准的 HTML 元素(如 div)一样进行自定义。

查看 CodePen 上 Mattia Astorino (@equinusocio) 的笔
标准环境 Web Component

CodePen 上。

我们为每个环境查看的两个示例都是使用同一个组件创建的。区别在于标准环境中的组件可以进行自定义并使用外部 CSS 进行选择。

Web Components 和组合

好的,所以使用 Web Components 实际上与使用普通 HTML 相同,不过正如我们所见,重要的是要遵循给定内容的范式和原则。我们需要牢记的是 Web Component 的**组合**。

正如 Google Web Fundamentals 所解释的:

组合是 Shadow DOM 中最不为人知的功能之一,但它可以说是最重要的。

在网页开发的世界中,组合是我们在HTML中声明式地构建应用程序的方式。不同的构建块(例如 `<div>`、`<header>`、`<form>`、`<input>`)组合在一起形成了应用程序。其中一些标签甚至可以相互配合。组合是像 `<select>`、`<details>`、`<form>` 和 `<video>` 这样的原生元素如此灵活的原因。这些标签中的每一个都接受某些HTML作为子元素,并对它们进行特殊处理。例如,`<select>` 知道如何将 `<option>` 和 `<optgroup>` 渲染成下拉菜单和多选组件。`<details>` 元素将 `<summary>` 渲染成一个可展开的箭头。甚至 `<video>` 也知道如何处理某些子元素:`<source>` 元素不会被渲染,但它们会影响视频的行为。真是太神奇了!

组合是我们通常在使用 HTML 时所做的事情。由于网页组件仅仅是带有 DOM 引用(而不是逻辑容器)的 HTML 元素,因此我们应该依靠组合来构建我们的组件和任何子组件。如果您想一想 `<ul>` 和 `<select>`,您会注意到您声明式地组合这些元素以获得最终输出,并且您有特定属性可用于主组件(例如 `[readonly]`)或子组件(例如 `[selected]`)。这对网页组件也是如此,如果您正在构建一个自定义列表,请考虑构建主组件(`<custom-list>`)和子组件(`<custom-li>`)。使用 `[slot]` 元素,您可以定义子元素将被放置的位置,以及当没有传递子元素时将显示的占位符内容。

网页组件和无障碍性

另一个需要考虑的是我们称为 **无障碍性** 的这个“小”话题。由于我们正在创建全新的 HTML 元素,我们需要考虑元素的无障碍性,以便提供基本的语义角色、任何键盘导航以及用户的操作系统偏好,例如 减少运动高对比度 设置。

我强烈建议以下资源作为构建无障碍和包容性组件的参考,以及如何定义语义标记和如何实现基本的键盘导航。

结论

网页组件是网页开发中的一项新兴技术,因此,就构建它们的预期用途或最大化用途而言,还没有明确定义的最佳实践来指导我们。当您发现自己使用它们时,您从本文中可以得到的最重要的结论可能是,考虑它们是用于 **封闭上下文** 还是 **标准上下文**,然后问问自己 **WHI**

  • **W**ho 将使用此组件?
  • **H**ow much 这个人应该拥有自定义它的灵活性?
  • **I**s 此组件适用于所有人,还是特定受众?