Skip to main content

Web Components

· 5 min read
zgh
Front-end Engineer

Web Components 是浏览器原生支持的。详细见 MDN

三个核心概念:

  1. Custom Elements(自定义元素):
    1. 可以创建自己的 HTML 标签,类似 <my-component>,并且定义其行为和生命周期函数。
    2. 相关生命周期回调包括 connectedCallbackdisconnectedCallbackattributeChangedCallback
  2. Shadow DOM(影子 DOM):
    1. 提供了 DOM 和 CSS 的封装,防止组件内部的样式和结构影响到外部
    2. 使组件更具可复用性,避免了样式污染
  3. HTML Templates(HTML 模板):
    1. 通过 <template><slot> 元素,可以创建可复用的结构和内容插槽
    2. <template> 中的内容不会立即渲染,只有在 JS 中手动克隆和插入到 DOM 时才会渲染

示例:

// 定义一个自定义组件:
class MyComponent extends HTMLElement {
constructor() {
super();
// 创建 Shadow DOM 并指定为 "open"
const shadow = this.attachShadow({ mode: 'open' });

// 创建并添加元素到 shadow DOM 中
const wrapper = document.createElement('div');
wrapper.innerHTML = `
<h1>这是 Shadow DOM 内部的标题</h1>
<p>这个组件内部的样式不会受到外部的影响。</p>
`;

// 添加组件的样式,仅作用于 Shadow DOM 内部
const style = document.createElement('style');
style.textContent = `
h1 {
color: red;
}
p {
color: green;
}
`;

// 将样式和元素添加到 Shadow DOM 中
shadow.appendChild(style);
shadow.appendChild(wrapper);
}
}

// 注册自定义组件
customElements.define('my-component', MyComponent);

然后就可以在 html 中使用:<my-component />

打开控制台可以看到 Dom 中出现了 my-component 标签,还有 #shadow-root (open)

插槽

具名插槽:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<!-- 使用自定义组件,并向 slot 传递内容 -->
<my-card>
<h2 slot="title">自定义标题</h2>
<p slot="content">这是自定义的内容,插入到组件中。</p>
</my-card>

<script>
// 定义自定义元素
class MyCard extends HTMLElement {
constructor() {
super();
// 创建 Shadow DOM
const shadow = this.attachShadow({ mode: 'open' });

// 定义模板内容,包括插槽
shadow.innerHTML = `
<style>
div {
border: 1px solid #000;
padding: 10px;
width: 200px;
}
::slotted(h2) {
color: blue;
}
::slotted(p) {
color: green;
}
</style>
<div>
<slot name="title">默认标题</slot>
<slot name="content">默认内容</slot>
</div>
`;
}
}

// 注册自定义元素
customElements.define('my-card', MyCard);
</script>
</body>
</html>

插槽内的内容是默认内容,如果没有外部传递内容,组件将显示这些默认内容。

还有默认插槽,例如修改上面的模版:<div><slot></slot></div>,然后使用:<my-card>666</my-card>

模板

使用 template 元素可以更好地组织和封装组件的内容。这样可以在 JS 中通过模板克隆内容并插入到 Shadow DOM 中。

优势:

  • 性能优化:将模板与渲染分开,可以避免重复解析和创建 DOM 结构
  • 可复用性:模板可以很容易地克隆,适用于多次实例化的场景,尤其是创建大量相同结构的组件时

修改前面的例子:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<!-- 使用自定义组件,并向 slot 传递内容 -->
<my-card>
<h2 slot="title">自定义标题</h2>
<p slot="content">这是自定义的内容,插入到组件中。</p>
</my-card>

<template id="my-card-template">
<style>
div {
border: 1px solid #000;
padding: 10px;
width: 200px;
}
::slotted(h2) {
color: blue;
}
::slotted(p) {
color: green;
}
</style>
<div>
<slot name="title">默认标题</slot>
<slot name="content">默认内容</slot>
</div>
</template>

<script>
// 定义自定义元素
class MyCard extends HTMLElement {
constructor() {
super();
// 创建 Shadow DOM
const shadow = this.attachShadow({ mode: 'open' });

// 获取模板
const template = document.getElementById('my-card-template');
const templateContent = template.content.cloneNode(true);

// 将模板内容添加到 Shadow DOM
shadow.appendChild(templateContent);
}
}

// 注册自定义元素
customElements.define('my-card', MyCard);
</script>
</body>
</html>