Webcomponents:真的就这么简单!
2015年,我第一次听说了WebComponents、自定义元素以及神秘的Shadow DOM。浏览器对它们的支持——好吧——我们姑且称之为实验性的。
在如今充斥着 polyfill 的时代,Polymer 这个名字似乎很适合一个支持“Chrome 专属”技术的框架。但即使在当时,爱好者们也似乎确信:这就是未来。原因显而易见。深入理解浏览器如何解释元素,能够提供快速、流畅、可复用且可控制的用户体验。
我们在哪里
在早期采用者经历了对承诺的标准建议不断的重大变更之后,我们现在处于一个 Web 组件感觉稳定、流畅且性能极高的时代。更重要的是:它变得简单了。
设置
在这个例子中,我们不会使用任何第三方库,但我建议查看lit html以满足基本的数据绑定需求。
全部大写
所以,我们要做的是:创建一个自定义元素,将其文本内容转换为大写。虽然不太引人注目,而且与直接使用 CSS 相比,确实有点矫枉过正,但它很好地表达了我们的想法。所以我们开始吧:
测试.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test custom element</title>
<script src="all-caps.js" type="module">
</head>
<body>
<all-caps>this is uppercase</all-caps>
</body>
</html>
全部大写.js
// 1. create class extending HTMLElement
export class AllCaps extends HTMLElement {}
// 2. Define a new custom element
customElements.define('all-caps', AllCaps)
关于这两行代码有很多话要说。
首先,我们要扩展 HTMLElement。我们需要遵循一些必要的规范,我们会在下一步中讲解。
接下来,我们将“全大写”定义为自定义元素(浏览器支持应该不再是问题,但如果需要,请随意规范行为要点)
构造函数
到目前为止一切顺利。现在你的类需要一个构造函数。此函数在类初始化时执行。重要的是要理解你需要考虑嵌套和继续解释。虽然了解 JavaScript 如何处理这一点很有趣,但只需遵循以下规则就足够了:始终以 开头super()
。别担心,如果你忘记了,你会发现 this 不可用。话虽如此,我们的类现在看起来是这样的:
export class AllCaps extends HTMLElement {
constructor() {
super();
}
}
进入 Shadow DOM
DOM(文档对象模型)是我们经常使用的表达方式之一,无需过多思考。有人可能对 HTML 和 XML 的历史感兴趣,但让我们尝试通过示例来加深理解:
在 JavaScript 中,你可能想知道类似的东西是如何document.getElementById()
在不受上下文影响的情况下工作的。毋庸置疑,这是因为“document”访问的是全局 DOM 树(就像你的浏览器一样)。任何曾经与 XPath 或 iframe 斗争过的人,在处理分离的 DOM 时都会有一段痛苦的经历。另一方面,分离的文档允许真正封装元素。Shadow DOM(有时也称为“虚拟 DOM”)就是这样。它是一种“子 DOM”,其操作方式与自身文档相同,但不受 iframe 处理数据和状态的限制。这就是为什么 Shadow DOM 不会继承样式,并且在所有上下文中都能提供安全的复用性。听起来很棒,不是吗?你甚至可以决定“外部”是否可以访问你元素的 Shadow DOM:
export class AllCaps extends HTMLElement {
constructor() {
super();
// attach a shadow allowing for accessibility from outside
this.attachShadow({mode: 'open'});
}
}
此时运行test.html会显示一个空白页面,因为我们使用的是“新”的 DOM。但这并不意味着内容丢失了。虽然我更喜欢使用节点,但让我们先把代码打包一下,得到我们预期输出的第一个版本:
export class AllCaps extends HTMLElement {
constructor() {
super();
// attach a shadow allowing for accessibility from outside
this.attachShadow({mode: 'open'});
// write our uppercased text to the Shadow DOM
let toUpper = this.firstChild.nodeValue.toUpperCase();
this.shadowRoot.innerHTML = toUpper;
}
}
成功了!一切正常,刷新test.html应该就能看到预期结果了。
先进的
让我们尝试一些额外的基础知识。
应用样式
注意:我通常会以稍微不同的方式构造它,但是为了包含我们正在讨论的部分,让我们执行以下操作:
在构造函数之后,我们添加另一个名为“attachTemplate”的函数
attachTemplate() {
const template = document.createElement('template');
template.innerHTML = `
<style>
:host{
color: red;
}
</style>`;
this.shadowRoot.innerHTML += template.innerHTML;
}
你可能对“:host”感到好奇。这个选择器引用的是元素本身。为了执行这个函数,我们需要在构造函数中调用它:
this.attachTemplate()
注意,你也可以使用例如“connectedCallback”作为函数名,但本教程只讲解基础知识。
我们的类现在应该如下所示:
export class AllCaps extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
let toUpper = this.firstChild.nodeValue.toUpperCase();
this.shadowRoot.innerHTML = toUpper;
this.attachTemplate();
}
attachTemplate() {
const template = document.createElement('template');
template.innerHTML = `
<style>
:host{
color: red;
}
</style>`;
this.shadowRoot.innerHTML += template.innerHTML;
}
}
重新加载test.html现在不仅应该提供大写字母,还应该提供红色(请在实际场景中考虑单一责任)。
插槽
另一个(这里比较粗略的)介绍是使用插槽。插槽可以命名,也可以引用元素的完整内容。让我们尝试一下,掌握它的基本用法:
在我们的文件的文字字符串中,添加标签<slot></slot>
,从而产生以下attachTemplate函数
attachTemplate() {
const template = document.createElement('template');
template.innerHTML = `
<slot></slot>
<style>
:host{
color: red;
}
</style>`;
this.shadowRoot.innerHTML += template.innerHTML;
}
刷新浏览器,您会注意到标签的原始内容已添加到 DOM 中。
属性和数据
最后,我们来看一下属性。同样,这是一个毫无意义的例子,但我认为它很好地解释了这个概念。
在test.html中,我们将为标签添加属性“addition”,其值为“!”。
<all-caps addition="!">hi there</all-caps>
接下来,我们将再次编辑我们的模板字符串并添加到${this.addition}
我们的插槽后面。
attachTemplate() {
const template = document.createElement('template');
template.innerHTML = `
<slot></slot>
${this.addition}
<style>
:host{
color: red;
}
</style>`;
this.shadowRoot.innerHTML += template.innerHTML;
}
现在我们需要处理该属性,至少要考虑到它未被设置的情况。为此,我们可能需要创建一个新函数,但我还是会快速“破解”它。在构造函数中,在执行“attachTemplate”之前,我们可以添加
if(this.hasAttribute('addition')){
this.addition = this.getAttribute('addition')
} else {
this.addition = '';
}
我们的课程现在看起来像这样:
export class AllCaps extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
let toUpper = this.firstChild.nodeValue.toUpperCase();
this.shadowRoot.innerHTML = toUpper;
if(this.hasAttribute('addition')){
this.addition = this.getAttribute('addition')
} else {
this.addition = '';
}
this.attachTemplate();
}
attachTemplate() {
const template = document.createElement('template');
template.innerHTML = `
<slot></slot>
${this.addition}
<style>
:host{
color: red;
}
</style>`;
this.shadowRoot.innerHTML += template.innerHTML;
}
}
刷新浏览器即可查看结果。
结论
本教程旨在帮助您理解自定义元素和 Shadow DOM 的基本处理。正如开篇所述,您可能希望使用像 lit-html 这样的库来简化操作,并且您肯定希望代码更简洁一些(我在以身作则和尽可能保持代码简洁之间挣扎了很久)。但我希望这能给您一个良好的开端,并帮助您点燃深入探索的火花。
如今,我们可以假设 Web Components 将主导 Web 领域,并逐渐取代像 Angular 这样的性能密集型框架。无论您是职业生涯的起步阶段,还是久经沙场的 React 爱好者,熟悉 Web 的发展方向都是非常有意义的。尽情享受吧!
文章来源:https://dev.to/sroehrl/webcomponents-it-s-really-that-easy-96e