背景
Web Components 是什么?顾名思义,就是“网页组件”,我们先来看看MDN 上是怎么描述的:
Web Components 是一组 web 平台的技术API ,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的web 应用中使用它们。
从 MDN描述上来看,Web Components有以下几个关键词:技术 API,可重用、定制。
接下来我们详细了解下这项技术对我们前端开发有什么好的改进。
在目前大前端的开发背景下,React、Vue、Angular 等都支持组件化的开发,你可以将前端界面抽离成各种不同的组件,独立封装样式和交互细节,并且可以通过事件/属性的方式跟其他组件进行通信,在传统的 HTML 中我们虽然也可以通过 HTML 标签封装,但是其问题在于会存在样式污染,DOM 结构嵌套混乱,无法隔离等等问题,而Web Components的出现就很好的解决了以上这些问题,可以让我们用原生 Web 技术来实现类似于 React 等组件的包装。
Web Components 的组成
Web Components不是单一的规范,而是一系列的技术组成,包括Custom Element、Shadow DOM、HTML templates 三种技术规范。
Custom elements
一组JavaScript API,允许您定义custom elements
及其行为,然后可以在您的用户界面中按照需要使用它们。
Shadow DOM
一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
HTML templates
<templete>
和 <slot>
元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
兼容性
关于以上 3 项技术的兼容性,我们可以通过以下链接查阅:
- https://caniuse.com/?search=custom%20elements
- https://caniuse.com/?search=shadow%20dom
- https://caniuse.com/?search=html%20templates
接下来我们具体看看在一个 web components这 3 项技术分别的作用点是什么?
Custom Elements
先抛出代码:
<script>
class HelloHockor extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({
mode: "open",
});
const div = document.createElement("div");
div.className = "hello";
// 不会污染外界
const style = document.createElement("style");
style.innerHTML = `
.hello {
color: red;
}
`;
div.innerHTML = "hello world";
shadow.appendChild(div);
shadow.appendChild(style);
}
}
const tagName = "hello-hockor";
// 判断页面中是否已定义过该 tag
const isDefined = customElements.get(tagName);
if (!isDefined) {
customElements.define(tagName, HelloHockor);
}
</script>
接下来我们来详细解释下
- 首先我们定义一个 class Hello World,继承自 HTMLElement
- 在该 class 的 constructor 里面,我们可以通过 this.attachShadow 来创建一个 shadow dom
- 在 shadow dom 里面我们可以通过浏览原生的 DOM 方法创建标准 HTML 标签,添加内容
- 最后我们通过customElements.define 来定义我们输出的标签名。
CustomElementRegistry
CustomElementRegistry
接口提供注册自定义元素和查询已注册元素的方法,要获取它的实例,请使用 window.customElements
属性。
详细文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/CustomElementRegistry
我们这里主要使用他来定义一个新的标签,该方法接受以下3个参数:
- 表示所创建的元素名称的符合
DOMString
标准的字符串。注意,custom element 的名称不能是单个单词,且其中必须要有短横线。 - 用于定义元素行为的 类 。
可选参数
,一个包含extends
属性的配置对象,是可选参数。它指定了所创建的元素继承自哪个内置元素,可以继承任何内置元素。
总结
通过以上 JS 的定义,我们就可以在普通 DOM中使用这个 tag 来渲染我们自定义的内容了,这个 tag 是由我们自己控制的,而且如果你打开控制台,你会看到它的内部是被包裹在 shadow-root 里面的,并且其内部的样式并不会影响到外部同类名的样式。
demo 地址:https://g8vtnv.csb.app/1.html
Template
在上面的例子中,我们在shadow 中通过document.createElement的方式创建了我们内部需要的结构,但是这种过程式的写法是非常费力不讨好的,比如你现在需要修改 DOM 的嵌套顺序,你得重新写一大串的 dom 操作,或者是后续你想改个属性,也非常麻烦,还是我们常用的声明式结构方便,所以这里就引出了我们 web components 中的第二个关键内容 - template。
我们先来看看如何在 web components 中使用 template
<div id="app">
<div class="hello">i'm from browser</div>
<hello-world></hello-world>
<!--模板部分-->
<template id="hello">
<style>
.hello {
color: red;
}
</style>
<div class="hello">hello world</div>
</template>
</div>
<script>
class HelloWorld extends HTMLElement {
constructor () {
super()
const shadow = this.attachShadow({
mode: 'open'
})
// 这里我们直接获取页面中的 template
const div = document.getElementById('hello')
shadow.appendChild(div.content.cloneNode(true))
}
}
customElements.define('hello-world', HelloWorld)
</script>
这里注意的一下,我们使用Node.cloneNode()
方法将 template 的内容加到 shadow-root中。
demo地址:https://zl1xtk.csb.app/2.template.html
slot
熟悉Vue 的小伙伴可能用过 vue 中的 slot,可以先在模板中占个位置,后续我们再灵活的传入自己定义好的 DOM,而在 template 中,也是支持 slot的,接下来我们看个例子。
<div id="app">
<div class="hello">i'm from browser</div>
<hello-world>
<!--slot 应用-->
<span slot="username">hockor</span>
</hello-world>
<!--模板部分-->
<template id="hello">
<style>
.hello {
color: red;
}
</style>
<div class="hello">
hello,
<slot name="username">world</slot>
</div>
</template>
</div>
<script>
class HelloWorld extends HTMLElement {
constructor () {
super()
const shadow = this.attachShadow({
mode: 'closed'
})
const div = document.getElementById('hello')
shadow.appendChild(div.content.cloneNode(true))
}
}
customElements.define('hello-world', HelloWorld)
</script>
上面的 demo 中我们默认定义的 slot 是个 world,但是最终渲染出来的时候会被我们使用的<span slot="username">hockor</span>
给代替,甚至你还可以传入一个 嵌套 DOM结构,方便我们使用。
demo 地址:https://zl1xtk.csb.app/3.template-slot.html
Shadow DOM
最后我们再来看看web components 中的 shadow dom,其实我们之前已经用到它了,只是没有正式的介绍而已。
借用 MDN 的介绍:Web components 的一个重要属性是封装——可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净、整洁。其中,Shadow DOM 接口是关键所在,它可以将一个隐藏的、独立的 DOM 附加到一个元素上
这里,有几个 Shadow DOM 特有的术语:
- Shadow host:一个常规 DOM节点,Shadow DOM 会被附加到这个节点上。
- Shadow tree:Shadow DOM内部的DOM树。
- Shadow boundary:Shadow DOM结束的地方,也是常规 DOM开始的地方。
- Shadow root: Shadow tree的根节点。
mode 属性
const shadow = this.attachShadow({
mode: 'open'
})
我们在上面的例子中创建 shadow 的时候有传递一个 mode,他的可选值是 open / closed
那么有什么区别呢?其实很简单,当我们 mode = closed 的时候,我们通过 this.shadowRoot拿到是个 null,那么就不可以从外部获取 Shadow DOM 了,浏览器中的某些内置元素就是如此,例如<video>
,包含了不可访问的 Shadow DOM。
总结
好了,对于 web components 的基础认识我们先到这里,对于一个可在项目中实际使用的组件,我们肯定还需要有生命周期和 CSS 样式定义,后续我们再继续看看 这 2 个问题。
参考链接
原创不易,欢迎关注公众号《hockor》,查看更多内容~