2024年8月14日
引言
Web Component 是一种用于构建可复用用户界面组件的技术,开发者可以创建自定义的 HTML 标签,并将其封装为包含逻辑和样式的独立组件,从而在任何 Web 应用中重复使用,并且可以做到无框架跨框架。
不同于 Vue/React 等社区或厂商的组件化开发方案,Web Component 被定义在标准的 HTML 和 DOM 标准中。它由一组相关的 Web 平台 API 组成,也可以与现有的前端框架和库配合使用Web Component 的兼容性良好,可以在现代浏览器中直接使用,也可以通过 polyfill 兼容到旧版浏览器。
每个 Web Component 都具有自己的 DOM 和样式隔离,避免了全局 CSS 和 JavaScript 的冲突问题。它还支持自定义事件和属性,可以与其他组件进行通信和交互。实现这些主要基于其三个技术:
- Custom elements:一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
- Shadow DOM:一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突
- HTML templates:<template> 和 <slot> 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
如何简单创建一个Web Components
挂载
首先为这个项目创建一个文件夹,然后在里面为组件准备一个button.js文件,在这里将编写一个按钮组件,button.js文件内容如下:
- 模版
let templateTemp = `
<template id='button'><div class="button">按钮</div>
</template>
`;
- 样式
let styleTemp = `
.button{width:70px;height:50px;background-color:rgb(54, 54, 252);color: white;display: flex;justify-content: center;align-items: center;border-radius: 10px;user-select: none;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;cursor: pointer;
}
.button:hover{background-color:rgb(38, 38, 250);
}
.button:active{background-color:rgb(5, 5, 255);
}`;
- 将模版字符串转化为document节点
const parser = new DOMParser();
const doc = parser.parseFromString(templateTemp, "text/html");
const temp = doc.getElementById("button");
- 创建自定义组件
class button extends HTMLElement {constructor() {super();//创建一个影子节点,组件会挂载在上面,参数mode表示是否可被外部访问const shadow = this.attachShadow({ mode: "open" });//获取模版和样式const content = temp.content.cloneNode(true);const style = document.createElement("style");style.textContent = styleTemp;//挂载shadow.appendChild(style);shadow.append(content);}
}
customElements.define("zf-button", button);
- 接下来查看这个组件,比如在一个原生的index.html文件中
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><link rel="icon" type="svg+xml" href="public/logo.svg"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>My HTML File</title><link rel="stylesheet" type="text/css" href="style.css"><script src="script.js"></script>//导入文件<script src="button.js"></script>
</head>
<body>//直接使用模版<zf-button></zf-button>
</body>
</html>
这样一个简单的Web Component就挂载好了并且可以看到了。
现在这个按钮已经有最基本的样式,但相比Element组件库的按钮还不够丰富,比如按钮的类型,或大或小的,在用这个组件时可以以参数的方式进行切换,还有颜色等等,这些就涉及如何与组件通讯了,还有想让按钮有个图标,这样就要用到插槽。在处理这些问题前,先来了解Web Component的生命周期。
生命周期
一个Web Component有4个生命周期
- connectedCallback
首次插入文档DOM时调用,这个回调函数通常用于执行一些初始化操作,比如添加事件监听器、请求数据等等。在这个时候,元素已经被添加到了文档中,可以访问到this.shadowRoot里的 DOM 和其他元素。 - disconnectedCallback
从文档删除时调用,这个回调函数通常用于清理一些资源,比如取消事件监听器、停止定时器等等。在这个时候,元素已经不再被文档所包含,无法访问到 DOM 和其他元素。 - adoptedCallback
移动到新文档时调用,这个回调函数通常用于处理一些文档级别的操作,比如重新计算布局(重排)、修改样式等等。在这个时候,元素已经从原来的文档中移除,并被添加到了新的文档中。 - attributeChangedCallback
修改自身属性时调用,这个回调函数通常用于处理一些属性相关的逻辑,比如根据属性值的变化更新元素的样式、重新渲染元素等等。在这个时候,元素的属性已经被修改,可以通过新的属性值来进行相应的处理。
class button extends HTMLElement {constructor() {super();}connectedCallback() {console.log("首次插入文档");}disconnectedCallback() {console.log("从文档删除");}adoptedCallback() {console.log("移动到新文档");}attributeChangedCallback() {console.log("修改");}
}
组件通讯
组件通讯主要就是外部传递信息给组件内部和组件内部传递信息给外部。主要的实现在于shadowRoot的方法去操作组件的自定义属性。比如想要修改刚才按钮组件的文字,那么给组件添加自定义属性text:
<zf-button text="添加"></zf-button>
然后在生命周期connectedCallback中获取并赋值
connectedCallback() {if (this.hasAttribute("text")) {this.shadowRoot.querySelector(".button").innerHTML =this.getAttribute("text");}
}
再或者想要点击这个按钮后,文字变为修改,这个就得在生命周期attributeChangedCallback去监听text属性,如果有huan hu。
首先要添加observedAttributes数组,元素为自定义属性的名字,这样attributeChangedCallback才能监听。
static observedAttributes = ["text"];
接着在attributeChangedCallback,监听到text的变化,然后在shadowRoot中找到按钮组件进行修改。
attributeChangedCallback(name, oldValue, newValue) {if (name === "text") {this.shadowRoot.querySelector(".button").innerHTML =newValue;}
}
最后就是外部如何去修改组件的自定义属性,这里需要shadow节点可以被访问
const shadow = this.attachShadow({ mode: "open" });
然后给组件添加点击事件,在里面修改组件的属性值,这样的修改会被组件的attributeChangedCallback函数捕抓。
document.addEventListener("DOMContentLoaded", function () {document.querySelector("#btn").addEventListener("click", function () {document.querySelector("#btn").attributes.text.value = "修改";});
});
用Web Component构建组件库
这样的组件库相比针对Vue的element和针对react的vant,它不会受框架的限制,就是不能享受语言框架带来的便利。比如Vue的响应式属性和事件绑定,在原生的html不具备,然后其组件库也是不能使用的,只能用原生的方法去实现。这样的组件库我找到了:
Quark Design
https://quark-ecosystem.github.io/quarkd-docs/vue/#/
还有一些框架可以自己去搭建Web Component组件库,比如Omi框架,在这里用Web Component搭建的组件库,可以适配vue等语言系统的特性
OMI Web Components
https://omi.cdn-go.cn/home/latest/zh/