Web Components
· 阅读需 5 分钟
Web Components 是浏览器原生支持的。详细见 MDN
三个核心概念:
- Custom Elements(自定义元素):
- 可以创建自己的 HTML 标签,类似
<my-component>
,并且定义其行为和生命周期函数。 - 相关生命周期回调包括
connectedCallback
、disconnectedCallback
、attributeChangedCallback
等
- 可以创建自己的 HTML 标签,类似
- Shadow DOM(影子 DOM):
- 提供了 DOM 和 CSS 的封装,防止组件内部的样式和结构影响到外部
- 使组件更具可复用性,避免了样式污染
- HTML Templates(HTML 模板):
- 通过
<template>
和<slot>
元素,可以创建可复用的结构和内容插槽 <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 = `
`;
}
}
// 注册自定义元素
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>