Web Components 原生组件化
# 概念
Web Components 是一个浏览器原生支持的组件化方案,其支持我们创建自定义可重用的元素,使用时不需加载任何额外的模块,其实我们一直在使用这项技术,input、video 和 audio 等就是原生的 Web Components,只是如今我们自己也可以使用这项技术去创造组件。
Web Components 主要由三项技术组成:
- Custom Elements:允许自定义 HTML 元素。
- Shadow DOM:影子 DOM,一组 JavaScript API,用于将封装的“影子”DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,你可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
- HTML Templates:HTML 模板,template 和 slot 元素使你可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
# 创建 Custom Element
首先,定义一个新的 JavaScript 类来表示你的 Custom Element。这个类将扩展 HTMLElement
基类。
class MyComponent extends HTMLElement {
constructor() {
super();
// 在这里初始化组件状态等
}
}
# 注册 Custom Element
创建好类后,你需要使用 customElements.define
方法来注册您的元素。
customElements.define('my-component', MyComponent);
注册后,你就可以在 HTML 文件中使用新标签了:
<my-component></my-component>
确保你选择的标签名包含一个连字符(-),这是自定义元素命名的必要规则。
# 使用 Shadow DOM
Shadow DOM 允许开发者向 DOM 树中插入一个封装的 "影子" DOM 树,影子 DOM 与主文档的 DOM 分开,意味着它们的样式和脚本都是独立的。见下面的例子:
class MyComponent extends HTMLElement {
constructor() {
super();
// 创建影子 DOM 根结点
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
// 每当 custom element 连接到 DOM 时,运行这里的代码
this.shadowRoot.innerHTML = `
<style>
/* Shadow DOM 中的样式在这里定义,不会影响外部文档 */
:host { display: block; }
p { color: blue; }
</style>
<p>这是 Shadow DOM 中的内容</p>
`;
}
}
:host
伪类使你能够定义宿主元素的样式。
connectedCallback
是 Custom Element 生命周期的一部分,当 Custom Element 被附加到 DOM 时会被调用。
我们将一个参数 { mode: "open" }
传入 attachShadow()
。当 mode
设置为 "open"
时,页面中的 JavaScript 可以通过影子宿主的 shadowRoot
属性访问影子 DOM 的内部。
# 使用 template 和 slot
<template>
标签被用来声明一个 HTML 模板,当需要重复生成 DOM 元素时,可以用来实例化新的内容。
const template = document.createElement("template");
template.innerHTML = `
<p>这是模板中的内容,还包含了一个 slot 元素</p>
<slot name="my-text"></slot>
`;
在 JavaScript 中,你可以访问和克隆模板内容,然后将其附加到 Shadow DOM 中。
class MyComponent extends HTMLElement {
constructor() {
super();
// 深度克隆一份 template
const content = template.content.cloneNode(true);
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(content);
}
}
然后,你可以在 HTML 中填充插槽:
<my-component>
<span slot="my-text">这是新的内容</span>
</my-component>
# 生命周期回调
定义在自定义元素的类定义中的特殊回调函数。
class MyCustomElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = `...`;
this.addEventListener('click', (event) => {
console.log('元素被点击了!');
});
}
// 当自定义元素第一次被连接到文档 DOM 时被调用
connectedCallback() {
console.log('Custom element 被添加到文档。');
}
// 当自定义元素与文档 DOM 断开连接时被调用
disconnectedCallback() {
console.log('Custom element 被移除出文档。');
}
// 当自定义元素被移动到新文档时被调用
adoptedCallback() {}
// 当自定义元素的一个属性被增加、移除或更改时被调用
attributeChangedCallback(attrName, oldVal, newVal) {
console.log(`属性 ${attrName} 从 ${oldVal} 变成了 ${newVal}`);
}
}
# 优缺点
优点:
- 浏览器原生支持,不用加入任何依赖
- 多种场景适用,天生组件隔离
缺点:
- 跟主流的框架相比,书写较为复杂,需要开发者自己进行原生 dom 操作
- 若要写成单文件组件,需要采用模板字符串的写法,没有语法高亮,代码提示等