前端开发以惊人的速度发展。这一点从无数的文章、教程和 Twitter 线程中可见一斑,这些文章、教程和 Twitter 线程都在哀叹曾经相当简单的技术栈现状。在这篇文章中,我将讨论为什么 Web 组件是交付高质量用户体验的绝佳工具,无需复杂的框架或构建步骤,并且不会有变得过时的风险。在本系列的后续文章中,我们将深入探讨每个规范。
本系列假定您对 HTML、CSS 和 JavaScript 有基本的了解。如果您在其中一个领域感到薄弱,请不要担心,构建自定义元素实际上简化了前端开发中的许多复杂性。
文章系列
- Web 组件简介 (本文)
- 制作可重用的 HTML 模板
- 从头开始创建自定义元素
- 使用影子 DOM 封装样式和结构
- Web 组件的高级工具
Web 组件到底是什么?
Web 组件由三项独立的技术组成,它们协同使用
- 自定义元素。简而言之,这些是使用一组 JavaScript API 创建的、具有自定义模板、行为和标签名称(例如
<one-dialog>
)的完全有效的 HTML 元素。自定义元素在 HTML 活跃标准规范中定义。 - 影子 DOM。能够隔离 CSS 和 JavaScript,几乎就像一个
<iframe>
。这在 活跃标准 DOM 规范中定义。 - 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> — <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。最后,我们将通过研究更高层次的工具以及与当今流行的库和框架的集成来结束所有内容。
文章系列
- Web 组件简介 (本文)
- 制作可重用的 HTML 模板
- 从头开始创建自定义元素
- 使用影子 DOM 封装样式和结构
- Web 组件的高级工具
嗨,很棒的文章!很高兴看到更多关于 Web Components 的内容 :)
我注意到本系列的第 5 部分将介绍高级工具。我们一直在 http://www.open-wc.org 围绕工具和 Web Components 做很多工作
也许我们可以保持联系,并帮助您解决任何问题?很乐意听到您的消息!
我已经头疼了……但这是一个好的开始……期待接下来的内容 ;)
很棒的介绍!也许值得添加一个警告,这样人们就不会对自己进行 XSS(除非我漏掉了)。
对于我们新的庞大长期企业项目,我们选择了 Web Components 和原生 JS,而不是框架来进行 UI。该项目应该得到数十年的支持,我们不希望以后支持过时的框架(比如现在的 jQuery)或从一个框架迁移到另一个框架。因此,原生和 WC 作为官方标准规范将存在很长时间。
对于较小的项目和初创公司,框架可能是万能药,因为您可以开箱即用地创建很多东西。但在大型企业环境中,很难迁移到其他技术来保持最新,因此标准在这里发挥了作用,对于大型项目而言,WC 是一个非常好的解决方案。
我终于明白了 Shadow DOM 是什么
很棒的文章!
很好的教程,尽管我不建议在示例中使用 querySelector。与其他选择器相比,它非常慢,因为它需要解析 CSS。
感谢您的反馈。我不会说
querySelector
很慢,它只是没有某些替代方案那么快。它仍然可以在每毫秒运行高达 7000 个任务,当您真正考虑它的时候,这非常疯狂。对于大多数操作,querySelector
的便利性和多功能性使其成为一个不错的选择,尽管您完全正确,getElementById
和getElementsByClassName
比querySelector
快。这似乎是一篇关于 JS 框架时代被忽视的主题的非常必要的文章。我将非常感兴趣地关注这个系列。
Web Components 是进入这个“web”行业的令人最愉快的技术之一,也是当今所有 JavaScript 框架使用的相同技术。
嗨 Caleb,很高兴在这里看到关于 Web Components 的深入研究!感谢您抽出时间。我很好奇您为什么选择在本篇文章中介绍 HTML Imports。据我所知,该规范已在整个行业范围内被放弃,并将很快从 Chrome 中删除。大多数 Web Components 社区似乎已将 ESModules(未来可能包括 HTMLModules 和 CSSModules)作为首选的模块/重复数据删除策略。为什么在这里提及它,因为这可能会被误解为 Web Components 现在和未来使用的技术系列的积极组成部分?
嘿 Westbrook,这是一个公平的观点。我认为关于 HTML Imports 的常识已经足够多,值得一提。您完全正确,HTML Imports 已被弃用,取而代之的是 HTML 模块。这里的一些语言很令人困惑。我将研究如何更新它。
HTML Imports 曾经是 Web Component 规范的一部分,但已被弃用,并被 ES6 模块导入取代。Chrome 曾经从版本 36 到 72 支持它。但它已被删除。
顶部的部分“HTML Imports 可能成为……”应该涵盖 HTML 模块。HTML Imports 在 Chrome 中已经发布了好几年,现在正被弃用:https://www.chromestatus.com/features/5144752345317376
嘿 Eric,感谢您的反馈。我认为在写作/编辑过程中有些东西丢失了。我将更新它。
这是一篇很棒的文章,除了您不应该在循环时直接将
appendChild
附加到 DOM,因为这会导致 布局抖动(即触发大量的重绘)。一种选择是创建一个文档片段,将您的元素附加到该片段,然后将该片段附加到 DOM。我期待阅读本系列的其余部分 :)我会删除对 HTML 模块的引用。规范的那部分已被弃用,并且不会在将来使用。
嗨 Caleb!好文章 :) 我认为有 4 个规范构成了 Web Components,第四个是 ES Modules :) 不确定您想说多少,但这可能值得添加!
https://www.webcomponents.org/introduction
我们从今年年初就开始使用 Web Components 创建我们的设计系统。我们放弃了 shadowRoot 选项,因为我们有多种皮肤,并且还没有找到使该要求与 shadowRoot 一起工作的方法(至少现在还没有)。尽管我喜欢(作为设计师)封装代码的想法。您如何看待使用 Web Components 和 shadowRoot 来维护皮肤?
读者 Jon Nyman,在比赛结束之后,所以代表他发布
关于 CSS 和 Web Components 有点棘手。我知道有两种方法可以使用页面的 CSS。首先,您可以直接使用 DOM,而不是 Shadow Root。第二,您可以使用插槽,它使用页面的 CSS。然后,您可以为自定义元素命名空间您的 CSS,例如,如果您有自定义元素
<i-like-cheese>
,那么您的 CSS 可能如下所示