Web 组件简介

Avatar of Caleb Williams
Caleb Williams

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费积分!

前端开发以惊人的速度发展。这一点从无数的文章、教程和 Twitter 线程中可见一斑,这些文章、教程和 Twitter 线程都在哀叹曾经相当简单的技术栈现状。在这篇文章中,我将讨论为什么 Web 组件是交付高质量用户体验的绝佳工具,无需复杂的框架或构建步骤,并且不会有变得过时的风险。在本系列的后续文章中,我们将深入探讨每个规范。

本系列假定您对 HTML、CSS 和 JavaScript 有基本的了解。如果您在其中一个领域感到薄弱,请不要担心,构建自定义元素实际上简化了前端开发中的许多复杂性。

文章系列

  1. Web 组件简介 (本文)
  2. 制作可重用的 HTML 模板
  3. 从头开始创建自定义元素
  4. 使用影子 DOM 封装样式和结构
  5. Web 组件的高级工具

Web 组件到底是什么?

Web 组件由三项独立的技术组成,它们协同使用

  1. 自定义元素。简而言之,这些是使用一组 JavaScript API 创建的、具有自定义模板、行为和标签名称(例如 <one-dialog>)的完全有效的 HTML 元素。自定义元素在 HTML 活跃标准规范中定义
  2. 影子 DOM。能够隔离 CSS 和 JavaScript,几乎就像一个 <iframe>。这在 活跃标准 DOM 规范中定义
  3. HTML 模板。用户定义的 HTML 模板,只有在调用时才会渲染。<template> 标签在 HTML 活跃标准规范中定义

这些构成了 Web 组件规范。

HTML 模块 可能是该堆栈中的第四项技术,但它尚未在任何四大浏览器中实现。Chrome 团队已宣布 有意在未来的版本中实现它们

Web 组件通常在所有主要浏览器中可用,除了 Microsoft Edge 和 Internet Explorer 11,但 存在 polyfills 来填补这些空白

将其中任何一项称为 Web 组件在技术上都是准确的,因为该术语本身有点过度使用。因此,每项技术都可以独立使用或与其他任何一项技术组合使用。换句话说,它们不是相互排斥的。

让我们快速了解一下前三项技术。我们将在本系列的其他文章中深入探讨它们。

自定义元素

顾名思义,自定义元素是 HTML 元素,如 <div><section><article>,但我们可以通过浏览器 API 为其命名。自定义元素就像那些标准的 HTML 元素一样 - 尖括号中的名称 - 只是它们始终包含一个连字符,如 <news-slider><bacon-cheeseburger>。今后,浏览器供应商承诺不会创建在其名称中包含连字符的新内置元素,以防止冲突。

自定义元素包含自己的语义、行为、标记,并且可以在框架和浏览器之间共享。

class MyComponent extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `<h1>Hello world</h1>`;
  }
}
    
customElements.define('my-component', MyComponent);

查看笔:
自定义元素演示
由 Caleb Williams (@calebdwilliams)
CodePen 上。

在此示例中,我们定义了 <my-component>,这是我们自己的 HTML 元素。诚然,它功能不多,但这是自定义元素的基本构建块。所有自定义元素都必须以某种方式扩展 HTMLElement 才能在浏览器中注册。

自定义元素无需第三方框架,并且浏览器供应商致力于该规范的持续向后兼容性,几乎可以保证根据规范编写的组件不会受到破坏性 API 更改的影响。此外,这些组件通常可以 直接与当今最流行的框架一起使用,包括 Angular、React、Vue 以及其他一些框架,只需付出很少的努力。

影子 DOM

影子 DOM 是 DOM 的一个封装版本。这允许作者有效地将 DOM 片段彼此隔离,包括任何可以用作 CSS 选择器的内容以及与其关联的样式。通常,文档范围内任何内容都被称为光 DOM,而影子根内部的任何内容被称为影子 DOM。

使用光 DOM 时,可以使用 document.querySelector('selector') 选择元素,或者使用 element.querySelector('selector') 针对任何元素的子元素;同样,可以使用 shadowRoot.querySelector 针对影子根的子元素,其中 shadowRoot 是文档片段的引用 - 区别在于影子根的子元素无法从光 DOM 中选择。例如,如果我们在影子根内部有一个 <button>,调用 shadowRoot.querySelector('button') 将返回我们的按钮,但文档的任何查询选择器调用都不会返回该元素,因为它属于不同的 DocumentOrShadowRoot 实例。样式选择器的工作方式相同。

在这方面,影子 DOM 的工作方式有点像一个 <iframe>,其中内容与文档的其余部分隔离;但是,当我们创建一个影子根时,我们仍然可以完全控制页面的那一部分,但范围限定在一个上下文中。这就是我们所说的封装

如果您曾经编写过一个重用相同 id 或依赖于 CSS-in-JS 工具或 CSS 命名策略 (例如 BEM) 的组件,影子 DOM 有可能改善您的开发体验。

想象以下场景

<div>
  <div id="example">
    <!-- Pseudo-code used to designate a shadow root -->
    <#shadow-root>
      <style>
      button {
        background: tomato;
        color: white;
      }
      </style>
      <button id="button">This will use the CSS background tomato</button>
    </#shadow-root>
  </div>
  <button id="button">Not tomato</button>
</div>

除了 <#shadow-root> 的伪代码(它在这里用于区分没有 HTML 元素的影子边界)之外,HTML 是完全有效的。要将影子根附加到上面的节点,我们将运行类似于以下代码的内容

const shadowRoot = document.getElementById('example').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `<style>
button {
  color: tomato;
}
</style>
<button id="button">This will use the CSS color tomato <slot></slot></button>`;

影子根还可以使用 <slot> 元素包含来自其包含文档的内容。使用插槽将在您影子根中的指定位置放置来自外部文档的用户内容。

查看笔:
影子 DOM 样式封装演示
由 Caleb Williams (@calebdwilliams)
CodePen 上。

HTML 模板

恰如其分地命名的 HTML <template> 元素允许我们在正常的 HTML 流中创建可重用的代码模板,这些模板不会立即渲染,但可以在稍后使用。

<template id="book-template">
  <li><span class="title"></span> &mdash; <span class="author"></span></li>
</template>

<ul id="books"></ul>

上面的示例不会渲染任何内容,直到脚本使用模板、实例化代码并告诉浏览器如何处理它为止。

const fragment = document.getElementById('book-template');
const books = [
  { title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' },
  { title: 'A Farewell to Arms', author: 'Ernest Hemingway' },
  { title: 'Catch 22', author: 'Joseph Heller' }
];

books.forEach(book => {
  // Create an instance of the template content
  const instance = document.importNode(fragment.content, true);
  // Add relevant content to the template
  instance.querySelector('.title').innerHTML = book.title;
  instance.querySelector('.author').innerHTML = book.author;
  // Append the instance ot the DOM
  document.getElementById('books').appendChild(instance);
});

请注意,此示例创建了一个模板 (<template id="book-template">),而没有使用任何其他 Web 组件技术,再次说明该堆栈中的三项技术可以独立使用或组合使用。

表面上,使用模板 API 的服务的使用者可以编写任何形状或结构的模板,这些模板可以在稍后创建。网站上的另一个页面可能会使用相同的服务,但以这种方式构建模板

<template id="book-template">
  <li><span class="author"></span>'s classic novel <span class="title"></span></li>
</template>

<ul id="books"></ul>

查看笔:
模板示例
由 Caleb Williams (@calebdwilliams)
CodePen 上。

这就是我们对 Web 组件的介绍

随着 Web 开发越来越复杂,对我们这样的开发者来说,开始将越来越多的开发工作委托给不断成熟的 Web 平台本身将变得越来越有意义。Web Components 规范是一组低级 API,它将随着我们作为开发者的需求不断发展而不断发展和演变。

在下一篇文章中,我们将更深入地研究这部分的 HTML 模板。然后,我们将接着讨论自定义元素和 Shadow DOM。最后,我们将通过研究更高层次的工具以及与当今流行的库和框架的集成来结束所有内容。

文章系列

  1. Web 组件简介 (本文)
  2. 制作可重用的 HTML 模板
  3. 从头开始创建自定义元素
  4. 使用影子 DOM 封装样式和结构
  5. Web 组件的高级工具