前言
此文为 webcomponent 入门篇,是对于 webcomponent 的基础介绍,并通过一个简单的组件实现例子带你无痛入门 webcomponent,轻松了解 webcomponent。
温馨提示:老手可以直接跳过此文。
webcomponent 是什么
webcomponent 直译过来就是网页组件,它能够让我们不借助第三方框架也能写出自定义的组件,通过编写自定义组件,可以在你需要的任意节点方便快捷地使用组件,并且不用担心与文档内的其他部分冲突。
webcomponent 来源已久,从 1998 年微软开创的 HTML components,经过二十多年的演变,如今的 webcomponent 技术已经逐步成熟规范。
webcomponent 的主要组成
- 自定义元素(Custom element)
允许你定义 custom elements 及其行为,然后可以在你的用户界面中按照需要使用它们。
- Shadow DOM
用于将封装的 Shadow DOM 附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,你可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样。以下是它的详细解释:
- Shadow host:一个常规 DOM节点,Shadow DOM 会被附加到这个节点上。
- Shadow tree:Shadow DOM内部的DOM树。
- Shadow boundary:Shadow DOM结束的地方,也是常规 DOM开始的地方。
- Shadow root: Shadow tree的根节点。
- HTML template(HTML 模板)
<template>和<slot>元素使你可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。 如下所示,就是一个非常简单的html模板:
<template> <h1>Hello world</h1> </template>
webcomponent 概念参考来自MDN
webcomponent 的生命周期
| 生命周期 | 触发时机 |
|---|---|
| connectedCallback | 当自定义元素第一次被连接到文档 DOM 时被调用。 |
| disconnectedCallback | 当自定义元素与文档 DOM 断开连接时被调用。 |
| attributeChangedCallback(attrName, oldVal, newVal) | 当自定义元素的一个属性被增加、移除或更改时被调用。 |
| adoptedCallback | 当自定义元素被移动到新文档时被调用。 |
slot
与 vue 的用法几乎一模一样,可以设置默认插槽和具名插槽,如下代码所示。
- 自定义元素 custom-element,并设置插槽
<p><slot name="text">默认文案</slot></p>
- 使用插槽
<custom-element> <span slot="text">hello world!</span> </custom-element>
相对于第三方框架的优点
原生组件简单直接,符合直觉,不用加载任何外部模块,代码量小。
实现一个简单的 webcomponent
接下来,我们来一起使用 webcomponent 实现一个简单的按钮组件,样式如下图所示。
预告:整个编写过程是和 vue 非常相似的。从下面的例子我们将会体验到 vue 很大程度上参考了 webcomponent。
一、创建组件模板
使用 template 创建 html 模板,并在 template 内写组件样式,实现样式隔离。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<template id="webButtonTemplate">
<style>
.button {
display: inline-flex;
justify-content: center;
align-items: center;
width: 130px;
height: 40px;
background-color: green;
border: 0;
border-radius: 12px;
padding: 10px;
color: #fff;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
box-sizing: border-box;
}
.button > .button_icon {
width: 20px;
height: 20px;
margin-right: 6px;
}
.button > .button_text {
font-size: 14px;
}
</style>
<button class="button">
<img class="button_icon" />
<span class="button_text"></span>
</button>
</template>
</body>
</html>
二、创建一个类来指定 webcomponent 的功能
首先,因为我们 webcomponent 所有的功能都是基于 HTMLElement 扩展的,所以我们需要创建一个HTMLElement 的子类,constructor 中执行 super 方法调用父类构造器 (子类构造器中出现 this 引用之前必须先调用 super()),如下代码所示。
class WebButton extends HTMLElement {
constructor() {
super();
}
}
三、将一个 Shadow DOM 附加到自定义元素上
这一步是可选的,如果你需要将组件与外部隔离,使得组件内部的代码无法影响外部,创建一个封闭的空间。那么我们需要使用自定义元素的 attachShadow 方法将一个 Shadow DOM 附加到自定义元素上。
attachShadow 方法中可以选择两种模式(mode),如下:
- “open” ——外部页面中的 JS 可以访问 Shadow DOM(使用Element.shadowRoot)
- “closed” ——Shadow DOM 只能在 webcomponent 内访问。
class WebButton extends HTMLElement {
constructor() {
super();
this.attachShadow( { mode: 'closed' } );
}
}
四、组装组件
获取并克隆模板DOM,再将组件通过参数传递过来的属性写入到克隆的模板DOM之中,就可以完成一个简单的组件组装了。
class WebButton extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow( { mode: 'closed' } );
// 创建组件模板
var templateElem = document.getElementById('webButtonTemplate');
// 克隆模板的所有子元素(因为该模板可能会被其他自定义元素使用)
var content = templateElem.content.cloneNode(true);
// 获取实例上的参数,赋值到克隆出来的模板中
content.querySelector('.button>.button_icon').setAttribute('src', this.getAttribute('icon'));
content.querySelector('.button>.button_text').innerText = this.getAttribute('text');
shadow.appendChild(content);
}
}
五、注册组件
使用浏览器原生的 customElements.define() 方法,告诉浏览器元素与这个类关联,也就是我们常说的组件注册的过程。
class WebButton extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow( { mode: 'closed' } );
var templateElem = document.getElementById('webButtonTemplate');
var content = templateElem.content.cloneNode(true);
content.querySelector('.button>.button_icon').setAttribute('src', this.getAttribute('icon'));
content.querySelector('.button>.button_text').innerText = this.getAttribute('text');
shadow.appendChild(content);
}
}
window.customElements.define('web-button', WebButton);
注意:组件注册时使用的名字一定要用短横线相连,否则浏览器将无法识别这是 webcomponent
六、组件的使用
如下所示,我们通过使用组件标签,并通过属性传递参数,开头中的两个按钮就实现了。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<web-button text="圣诞按钮" icon="https://s3.bmp.ovh/imgs/2023/12/18/260c0252c96ff831.png" bgColor="green" color="#fff"></web-button>
<web-button text="新年按钮" icon="https://s3.bmp.ovh/imgs/2023/12/18/b52b5a1b0c7147cb.png" bgColor="red" color="yellow"></web-button>
</body>
</html>
完整代码参考如下:
后记
文章到此结束,感谢阅读。本文是对于webcomponent的基础介绍,下一篇文章将会进一步介绍 webcomponent 的其他特性及使用方法,欢迎持续关注。