赞
踩
目录
随着项目体量的增大,组件化和模块化的优势也愈发明显了,构建可重复使用、独立、可互操作的组件变得尤为重要,在JS中我们可以通过class和函数对代码解耦,使某段代码可以复用。在TS中我们也可以通过模块对代码进行模块化开发,在HTML页面中同样有一种技术可以实现独立的、可复用的组件,这便是本篇文章讲到的Web Components
Web Components主要包括Custom Elements、Shadow DOM、HTML Templates和JavaScript这四部分,在后文及后续的文章中我会详细展开说说
在熟悉web组件之前,我们可以了解一下早期的开发人员如何进行业务组件复用的?
聊聊我使用过的三种复用方式
一是使用JQ的load()进行ajax请求,将结果渲染到某个div或者组件中,以我原先公司的页面为例子,可以将图中画框的部分分别在多个html页面中实现,然后使用JQ进行请求加载到主页的标签中,达到组件化效果,此时如果要传递参数则可以通过url或者共用localstorage等形式共享状态
这样做确实可以将某个页面或者标签模块进行复用,但是缺点也很明显,一是页面加载是异步的,需要通过ajax请求html文件的形式完成,二是传参的方式仅限于query的方式,复杂的参数支持率较为薄弱
第二种是Iframe的方式,这种方式和jq的load类似,同样拥有独立上下文,可以在当前环境使用自己的CSS和JS,并且在加载时独立于主页面。当然这么做的缺点也是有的,和load函数一样,它的页面单独加载和渲染多出了性能开销,以及异步加载问题
最后一种是使用JS的代码进行动态HTML拼接,介于其强大的兼容性,动态HTML拼接在早期的前端开发中被广泛使用,并且能够在绝大多数浏览器上良好运行,比如:
- function createCustomTag(tagName, text, attributes) {
- var tag = "<" + tagName;
- for (var attr in attributes) {
- if (attributes.hasOwnProperty(attr)) {
- tag += " " + attr + '="' + attributes[attr] + '"';
- }
- }
- tag += ">" + text + "</" + tagName + ">";
- return tag;
- }
这么做的好处是兼容性高 ,灵活性强,原生JS即可支持;但是其缺点是使JS语法以及CSS样式的隔离变得困难,代码可读性和可维护性降低,最终也被摒弃。
那么回到新生代的web components,它能解决什么问题,又有什么注意点?感兴趣的话就接着往下看吧
自定义元素(Custom Elements)在许多框架和UI组件中广泛使用,比如elementUI: <el-xxx></el-xxx>;vant:<van-xxx />等。开发者可以通过创建自定义的HTML元素,使其在页面中表现和使用类似于内置的HTML元素。开发者通过自定义元素创建自定义的HTML元素,使其在页面中表现和使用类似于内置的HTML元素,它是通过 JavaScript 创建一个自定义元素类,并通过继承HTMLElement类来定义其行为和样式。
首先是创建标签,通过继承HTMLElement类的方式来创建自定义标签的行为和样式。
在构造函数中可以进行自定义标签的初始化工作,例如设置默认属性、添加事件监听器等
- class MyCustomElement extends HTMLElement {
- constructor() {
- super();
- this.textContent = "my-custom-element"
- // 自定义元素被创建时的初始化逻辑
- }
- }
此外自定义标签类可以包含以下几种函数:
使用示例可以参考以下代码(具体效果及用法会在后文贴出)
- class MyCustomElement extends HTMLElement {
- constructor() {
- super();
- // 自定义元素被创建时的初始化逻辑
- this.textContent = "my-custom-element"
- }
- connectedCallback() {
- // 元素被插入到DOM时调用
- console.log("元素被插入到DOM");
- }
- disconnectedCallback() {
- // 元素从DOM中移除时调用
- console.log("元素从DOM中移除");
- }
- adoptedCallback() {
- // 元素被移动到新文档时调用
- console.log("元素移动到新文档");
- }
- attributeChangedCallback(attrName, oldValue, newValue) {
- // 元素的属性被添加、删除或修改时调用
- console.log(`${attrName}属性的旧值:${oldValue},新值:${newValue}`);
- }
- // 或使用静态属性代替get方法
- static get observedAttributes() {
- // 指定要监听的元素的属性数组
- return ['name', 'date'];
- }
- }
在创建标签后,我们需要通过customElements.define来定义一个自定义标签
customElements.define("my-custom-element", MyCustomElement)
tips:需要注意的是创建的标签的中间必须带 '-' 短横线,与原生标签隔开,比如:custom-element,而像:customElement,-customElement,customElement-,这几种是无法作为自定义名称使用的。
define函数可以传入三个参数,第三个可选参数是ElementDefinitionOptions,这个参数在使用自定义元素继承时才会用到,当我们定义一个自定义元素时,可以选择让它继承自内置的 HTML 元素。参考下面的代码
- <!DOCTYPE html>
- <html lang="en">
-
- <head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>CustomElements</title>
- </head>
-
- <body>
- <button is="my-custom-button"></button>
- <script>
- customElements.define("my-custom-button", class extends HTMLButtonElement {
- constructor() {
- super()
- this.textContent = "按钮";
- }
- connectedCallback() {
- this.addEventListener("click", () => console.log("点击了"))
- }
- }, { extends: "button" })// 继承自原生的按钮标签
- </script>
- </body>
-
- </html>
需要注意的是, 自定义标签类同样需要继承于对应的HTML标签类(比如:HTMLButtonElement),其次在标签中使用时需要增加is属性,相当于是对button进行拓展
使用自定义元素的方式有两种,分别是通过document.createElement('my-custom-element')和直接在页面中使用<my-custom-element></my-custom-element>标签,这和原生语法一致,只需要把常用的div,a,span等标签换成自定义的标签即可
- <body>
- <my-custom-element>my-custom-element</my-custom-element>
- <script>
- const ele = document.createElement("my-custom-element")
- ele.textContent = "my-custom-element"
- document.body.appendChild(ele)
- </script>
- </body>
通过customElements.get(elemName)可以获取标签为elemName的自定义标签类,如果在定义之前获取则显示未定义
- console.log(customElements.get(elemName));// undefined
- customElements.define(elemName, MyCustomElement)
- console.log(customElements.get(elemName).name);// class MyCustomElement extends HTMLElement {...}
使用customElements.whenDefined(elemName)函数可以在标签定义时触发回调函数
- setTimeout(() => {
- customElements.define(elemName, MyCustomElement)
- }, 1000)
- customElements.whenDefined(elemName).then(console.log)// class MyCustomElement
- console.log(customElements.get(elemName));// undefined
升级标签的目的是将自定义标签(ele)和自定义标签的构造函数或类(MyCustomElement)进行绑定,使标签(ele)可以访问类(MyCustomElement)的属性及方法。通过customElements.upgrade(ele)函数可以对自定义标签进行升级操作
- class MyCustomElement extends HTMLElement {
- bgColor = "red"
- constructor() {
- super();
- }
- }
- const elemName = "my-custom-element"
- const ele = document.createElement(elemName)
- customElements.define(elemName, MyCustomElement)
- console.log(Reflect.ownKeys(ele), ele.bgColor);// [] undefined
- customElements.upgrade(ele);// 升级ele,使其与自定义标签类绑定,也就是说可以访问MyCustomElement的属性及方法
- console.log(Reflect.ownKeys(ele), ele.bgColor);// ['bgColor'] red
最后我们结合上面的知识点,实现一个完整的自定义标签的示例
- <!DOCTYPE html>
- <html lang="en">
-
- <head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>CustomElements</title>
- </head>
-
- <body>
- <iframe src="./temp.html" width="100" height="100"></iframe>
- <script>
- const elemName = "my-custom-element"
- const ele = document.createElement(elemName)
- const iframeEle = document.querySelector("iframe")
- class MyCustomElement extends HTMLElement {
- bgColor = "red"
- constructor() {
- super();
- // 自定义元素被创建时的初始化逻辑
- this.textContent = elemName
- }
- connectedCallback() {
- // 元素被插入到DOM时调用
- console.log("元素被插入到DOM");
- }
- disconnectedCallback() {
- // 元素从DOM中移除时调用
- console.log("元素从DOM中移除");
- }
- adoptedCallback() {
- // 元素被移动到新文档时调用
- console.log("元素移动到新文档");
- }
- attributeChangedCallback(attrName, oldValue, newValue) {
- // 元素的属性被添加、删除或修改时调用
- console.log(`${attrName}属性的旧值:${oldValue},新值:${newValue}`);
- }
- // 或使用静态属性代替get方法
- static get observedAttributes() {
- // 指定要监听的元素的属性数组
- return ['name', 'date'];
- }
- }
- customElements.whenDefined(elemName).then(() => {
- const tempBox = iframeEle.contentDocument // 获取iframe的dom
- document.body.appendChild(ele)// 元素被插入到DOM
- ele.setAttribute("name", elemName)// name属性的旧值:null,新值:my-custom-element
- ele.setAttribute("date", "date")// date属性的旧值:null,新值:date
- ele.setAttribute("name", "my-custom-element2")// name属性的旧值:my-custom-element,新值:my-custom-element2
- ele.remove()// 元素从DOM中移除
- // 元素移动到新文档,通过iframe进行举例
- tempBox.body.appendChild(tempBox.adoptNode(ele))// 元素被插入到DOM
- })// 异步检查自定义标签定义,标签定义时触发该函数
- console.log(customElements.get(elemName));// 获取自定义标签,此时未定义
- console.log(ele instanceof MyCustomElement); // false
- iframeEle.onload = () => {
- setTimeout(() => {// 加个setTimeout明显一点
- customElements.define(elemName, MyCustomElement)
- console.log(Reflect.ownKeys(ele), ele.bgColor);// [] undefined
- customElements.upgrade(ele);// 升级ele,使其与自定义标签类绑定,也就是说可以访问MyCustomElement的属性及方法
- console.log(Reflect.ownKeys(ele), ele.bgColor);// ['bgColor'] red
- console.log(ele instanceof MyCustomElement);// true
- console.log(customElements.get(elemName).name);// MyCustomElement , 获取自定义标签,此时已定义
- }, 1000);
- }
- </script>
- </body>
-
- </html>
上述代码展示了一个简单的自定义元素的定义和使用过程,包括自定义元素的构造函数、移动、生命周期回调方法以及属性变化回调方法的使用,以及customElements中的几种方法
Custom Elements允许我们创建自定义的HTML元素,使其在页面中表现和使用类似于内置的HTML元素。我们可以通过继承HTMLElement类来定义自定义元素的行为和样式,并在构造函数中进行初始化工作。通过类的机制,我们可以达到复用自定义元素的目的。后面的系列文章我会基于其强大的Api与Shadow DOM和HTML Templates实现组件化效果,敬请期待。
文章到这也就结束了,感谢你的阅读,如果觉得文章对你有帮助的话,还望三连支持一下作者,感谢!
WebComponents/CustomElements.html · 阿宇的编程之旅/myCode - Gitee.com
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。