当前位置: 首页 > news >正文

Web Components详解-Custom Elements

目录

引言

演变过程

概述

使用方式

创建标签

定义标签

使用标签

获取标签

异步定义标签

升级标签

完整案例

结语

相关代码

参考文章


引言

随着项目体量的增大,组件化和模块化的优势也愈发明显了,构建可重复使用、独立、可互操作的组件变得尤为重要,在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"// 自定义元素被创建时的初始化逻辑}
}

此外自定义标签类可以包含以下几种函数:

  • 连接回调(connectedCallback):自定义元素被插入到DOM树中时调用
  • 断开回调(disconnectedCallback):自定义元素从DOM树中删除时调用
  • 移动回调(adoptedCallback):当自定义元素被移动到新文档时调用
  • 属性变化回调(attributeChangedCallback):自定义元素的属性被添加、删除或修改时调用
  • 静态属性(observedAttributes):指定attributeChangedCallback要监听哪些属性的数组

使用示例可以参考以下代码(具体效果及用法会在后文贴出)

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的domdocument.body.appendChild(ele)// 元素被插入到DOMele.setAttribute("name", elemName)// name属性的旧值:null,新值:my-custom-elementele.setAttribute("date", "date")// date属性的旧值:null,新值:dateele.setAttribute("name", "my-custom-element2")// name属性的旧值:my-custom-element,新值:my-custom-element2ele.remove()// 元素从DOM中移除// 元素移动到新文档,通过iframe进行举例tempBox.body.appendChild(tempBox.adoptNode(ele))// 元素被插入到DOM})// 异步检查自定义标签定义,标签定义时触发该函数console.log(customElements.get(elemName));// 获取自定义标签,此时未定义console.log(ele instanceof MyCustomElement); // falseiframeEle.onload = () => {setTimeout(() => {// 加个setTimeout明显一点customElements.define(elemName, MyCustomElement)console.log(Reflect.ownKeys(ele), ele.bgColor);// [] undefinedcustomElements.upgrade(ele);// 升级ele,使其与自定义标签类绑定,也就是说可以访问MyCustomElement的属性及方法console.log(Reflect.ownKeys(ele), ele.bgColor);// ['bgColor'] redconsole.log(ele instanceof MyCustomElement);// trueconsole.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

参考文章

Web components

Web Component - Web API 接口参考 | MDN

相关文章:

Web Components详解-Custom Elements

目录 引言 演变过程 概述 使用方式 创建标签 定义标签 使用标签 获取标签 异步定义标签 升级标签 完整案例 结语 相关代码 参考文章 引言 随着项目体量的增大&#xff0c;组件化和模块化的优势也愈发明显了&#xff0c;构建可重复使用、独立、可互操作的组件变得…...

批量将excel文件按照分类生成多个excel文件

要批量将Excel文件按照分类生成多个Excel文件&#xff0c;文件名为分类名&#xff0c;可以使用Python中的pandas库来实现。下面是示例代码&#xff1a; import pandas as pd import os def split_excel_by_category(file_path, category_column, output_folder): # 读取Ex…...

PCL 点云组件聚类

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 该算法与欧式聚类、DBSCAN聚类很是类似,聚类过程如下所述: 1. 首先,我们需要提供一个种子点集合,对种子点集合进行初始的聚类操作,聚类的评估器(即聚类条件),可以指定为法向评估,也可以是距离评估,以此我…...

学习Linux的注意事项(使用经验;目录作用;服务器注意事项)

本篇分享学习Linux过程中的一些经验 文章目录 1. Linux系统的使用经验2. Linux各目录的作用3. 服务器注意事项 1. Linux系统的使用经验 Linux严格区分大小写Linux中所有内容以文件形式保存&#xff0c;包括硬件&#xff0c;Linux是以管理文件的方式操作硬件 硬盘文件是/dev/s…...

Java字符串

文章目录 String类String的特性String对象的创建String常用方法 StringBuilder类StringBuffer类StringBuffer对象的创建StringBuffer类的常用方法 String、StringBuffer、StringBuilder区别 存放的位置 java.lang.*; 继承的父类 java.lang.Object 实现的接口 java.io.Serializa…...

vue3中props值类型和值的类型

...

windows下安装go环境 和vscode中go扩展

1. 首先安装GO Go下载地址&#xff1a;go.dev 选择相对应的版本&#xff0c;下载&#xff0c;运行安装程序&#xff0c;并打开命令提示符&#xff0c;运行 go env &#xff0c;确认已经安装go 注意关注其中GOPATH和GOROOT&#xff0c;这两个地址可以在系统环境变量中进行设置…...

C++ 面试题(一)--C++基础,面向对象,内存管理

目录 1.part1 C基础 1 C特点 2 说说C语言和C的区别 3 说说 C中 struct 和 class 的区别 4 include头文件的顺序以及双引号""和尖括号<>的区别 5 说说C结构体和C结构体的区别 6 导入C函数的关键字是什么&#xff0c;C编译时和C有什么不同&#xff1f; 7…...

ARTS打卡第二周之链表环的检测、gdb中disassemble的使用、底层学习建议、学习分享

Algorithm 题目&#xff1a;链表中环的检测 自己的分析见博客《检测链表中是否存在环》 Review disassemble command是我读的一篇英语文章&#xff0c;这篇文章主要是介绍gdb反汇编命令的使用和参数。自己为了能够演示这篇文章里边的内容&#xff0c;特意自己使用汇编语言编…...

皕杰报表(BIOS Report)中设置序号的方法之二

在皕杰报表如何设置序号系列之一里&#xff0c;我们用ds.#0来实现了序号&#xff0c;用ds.#0得到的数据库中选取的记录的序号。有些情况下&#xff0c;记录序号在报表中不是按照顺序显示的&#xff0c;而是在报表中又通过排序或分组后的结果显示的&#xff0c;例如&#xff1a;…...

nacos总结黑马

SpringCloud实用篇02 0.学习目标 1.Nacos配置管理 Nacos除了可以做注册中心&#xff0c;同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我…...

【简单bat】写一个定时关机的批处理程序

感谢我那可爱的GPT助手对代码的优化工作。 echo off REM Author: liyang TEL 155-4089-9552echo 欢迎使用自动关机计划设置脚本&#xff01;by LiYang echo.:choose_action set /p "action_choice请选择要执行的操作&#xff08;输入1设置定时关机任务&#xff0c;输入2删…...

Oracle 19c 启动和关闭实例保存PDB状态

简介&#xff1a; 十年以上 MySQL Oracle DBA从业者&#xff0c;MySQL 5.7 OCP&#xff0c; 微信号: jinjushuke 当前有一个PDB 打开模式为READ WRITE [oracleDGMOGGM19C ~]$ sql sys192.168.3.107:1521/pdb1 as sysdba SQLcl: Release 19.1 Production on Wed Aug 23 10:19:…...

计算机毕设 基于机器学习的餐厅销量预测 -大数据 python

文章目录 0 前言餐厅销量预测模型简介2.ARIMA模型介绍2.1自回归模型AR2.2移动平均模型MA2.3自回归移动平均模型ARMA 三、模型识别四、模型检验4.1半稳性检验(1)用途(1)什么是平稳序列?(2)检验平稳性 ◆白噪声检验(纯随机性检验)(1)用途(1)什么是纯随机序列?(2)检验纯随机性 五…...

layui 多选限制选择3个

可以使用 layui 的表单组件进行多选&#xff0c;然后通过 JavaScript 代码限制用户最多只能选择 3 个选项。具体的实现步骤如下&#xff1a; 在 layui 的表单组件中&#xff0c;使用多选框进行多选。 <div class"layui-form-item"><label class"layu…...

PyQt PySide6 QMessageBox使用教程

PySide6 是一个 Python 绑定到 Qt6 库的库&#xff0c;允许你在 Python 中创建 Qt6 应用程序。QMessageBox 是一个提供模式对话框&#xff0c;用于显示消息、询问问题或获取简单的输入的类。 以下是使用 PySide6 中的 QMessageBox 的基本方式&#xff1a; 导入必要的模块: f…...

Visual Studio软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 Visual Studio是微软公司开发的一款集成开发环境&#xff08;IDE&#xff09;&#xff0c;广泛应用于Windows平台上的应用程序和Web应用程序的开发。以下是Visual Studio软件的主要特点和功能&#xff1a; 集成开发环境&#x…...

VB电脑销售系统设计与实现

前 言 本文论述了销售管理系统的开发过程,它包括了软件的开发环境,开发工具以及相关技术、系统分析、系统设计、实现等内容。 本系统能为企业繁重的工作带来极大的方便,提高了工作效率及工作准确性,能准确无误地查找货物的综合信息,为企业减少了许多工作细节。 为了使此…...

如何访问MySQL错误日志

通常情况下&#xff0c;MySQL中出现减速、崩溃或其他意外行为的根本原因可以通过分析错误日志来确定。在Ubuntu系统中&#xff0c;MySQL的默认位置是/var/log/mysql/error.log。在许多情况下&#xff0c;使用less程序最容易读取错误日志&#xff0c;这是一个命令行实用程序&…...

redis主从复制详解

目录 前言&#xff1a; 分布式系统 主从模式 主从同步连接过程 replication id作用 offset作用 psync命令&#xff08;psync replicationId offset) 全量复制 全量复制过程 部分复制 部分复制过程 实时复制 前言&#xff1a; redis为了保证高可用&#xff0c;它支持…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建

华为云FlexusDeepSeek征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...