初步认识 Web Components 并实现一个按钮

目录

1.Web Components 基本概念

1.1 三个场景

1.2 是什么

2.使用 Custom Elements 实现一个按钮

2.1 概念介绍

2.1.1 Shadow DOM

2.1.2 Element.attachShadow()

2.1.3 在组件中 使用 Shadow DOM 基本步骤

2.1.4 attributeChangedCallback

2.1.5 get observedAttributes

2.1.6 dispatchEvent()

2.1.7 CustomEvent

2.2 实践出真知

2.2.1 初步实现按钮

2.2.2 给按钮加属性-传递

2.2.3 给按钮加属性-映射

2.2.4 给按钮加事件-普通事件

2.2.5 给按钮加事件-自定义事件


通过这篇文章,你可以学习到:

  • 如何使用原生 JavaScript 构建一个 Web Component
  • 如何在应用程序中使用 Web Component

1.Web Components 基本概念

1.1 三个场景

  • Vue2 升级到 Vue3 后,饿了么官方组件库也被迫从 ElementUI 升级到 ElementPlus,聪明的你不禁会发出疑问:难道每次框架升级都需要升级组件库吗?
  • xx项目,参考了个 React 开源项目,但开源项目组件用 React 写的,而我得用 Vue 开发
  • xx项目,作为总集公司,其他厂商需要使用总集提供的组件库,他们不得不投入时间成本学习 Vue3,老板因此很生气

针对上面的场景,预言家 Google 早在2011年就推出了解决方案 —— Web Component

1.2 是什么

Web Components 也被叫做 Custom Elements,它已经成为 浏览器标准 API

下面是各大浏览器针对 Web Components(Custom Elements) 的支持情况:

Custom Elements (V1) | Can I use... Support tables for HTML5, CSS3, etcicon-default.png?t=N7T8https://caniuse.com/custom-elementsv1

开发者可以在 Custom Elements 中,封装结构(HTML)、样式(CSS)和行为(JavaScript),最终生成自定义元素标签(使用方式类似于原生 html 标签),不会受框架限制

举个例子:

<!-- 原生 html 按钮 -->
<button name="button">按钮</button><!-- ElementPlus 按钮 -->
<el-button type="success">按钮</el-button><!-- Custom Elements -->
<my-button text="按钮"></my-button>

2.使用 Custom Elements 实现一个按钮

2.1 概念介绍

温馨提示:也可以食用完下面的实践出真知,再回头阅读这儿的概念介绍,这样更容易理解

2.1.1 Shadow DOM

一种将 DOM、样式、行为 封装在一个可重用的、封装的组件中的技术

Shadow DOM 可以帮助开发者避免样式和 DOM 冲突,提高代码的可维护性和可重用性(大家可以回忆一下修改 Ionic 组件样式时的那种感觉)

它允许开发者创建自定义元素,这些元素可以在页面上使用,就像普通的 HTML 元素一样

我们看下 Ionic 官网按钮示例,可以看出 ionic 组件就是用 Shadow DOM 写的自定义元素

2.1.2 Element.attachShadow()

该方法返回 ShadowRoot 对象,用于将一个 Shadow DOM 附加到 指定元素 上

  // 通过 Element.attachShadow() 获取 ShadowRoot 对象// open shadow root:元素可以从 js 外部访问根节点// closed 拒绝从 js 外部访问关闭的 shadow root 节点const shadowRoot = this.attachShadow({ mode: "open" });// 把 Shadow DOM 附加到 template 上shadowRoot.appendChild(templateDOM.content.cloneNode(true));

关于操作 shadow root 节点的必要性:这个当然很重要了

比如修改 Shadow Dom 中某个节点的 innerHTML

constructor() {// ...// 获取按钮文字 DOMthis.btnTextDOM = shadowRoot.querySelector(".button-inner");
}
render() {this.btnTextDOM.innerHTML = this.text;
}

2.1.3 在组件中 使用 Shadow DOM 基本步骤

总体思路:创建组件模板,给组件加 Shadow DOM

具体步骤:

  1. 创建一个 <template> 元素,将组件的 HTML 结构放在其中
  2. 使用 Element.attachShadow() 方法将 Shadow DOM 附加到组件元素上
  3. 将 <template> 元素的内容复制到 Shadow DOM 中

2.1.4 attributeChangedCallback

当自定义元素的 属性 变化(增加、移除、更改)时调用

attributeChangedCallback(name, oldVal, newVal) {this[name] = newVal;this.render();
}

注意:此方法通常与 get observedAttributes() 结合使用

2.1.5 get observedAttributes

如果需要在属性变化后,在 attributeChangedCallback 回调函数中执行某些操作,则必须监听这个属性

通过定义 get observedAttributes() 来监听属性变化

static get observedAttributes() {return ["text"];
}

注意:

  • get observedAttributes() 方法只会在类内部进行调用,不会被实例化对象调用
  • 此方法通常与 attributeChangedCallback() 结合使用

2.1.6 dispatchEvent()

dispatchEvent() 方法是用来触发指定事件的

它接受一个 Event 对象作为参数,该对象描述了要触发的事件的类型、是否冒泡、是否可以取消等信息

调用 dispatchEvent() 方法后,会在当前元素上触发指定的事件,并且事件会沿着 DOM 树向上传播,直到到达根节点或者被取消

// 获取要触发事件的元素
const myElement = document.querySelector('#my-element');// 创建一个自定义事件
const myEvent = new CustomEvent('my-event', {detail: {message: 'Hello world!'}
});// 触发自定义事件
myElement.dispatchEvent(myEvent);

EventTarget.dispatchEvent() - Web API 接口参考 | MDNEventTarget 的 dispatchEvent() 方法会向一个指定的事件目标派发一个 Event,并以合适的顺序(同步地)调用所有受影响的 EventListener。标准事件处理规则(包括事件捕获和可选的冒泡过程)同样适用于通过手动使用 dispatchEvent() 方法派发的事件。icon-default.png?t=N7T8https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/dispatchEvent

2.1.7 CustomEvent

CustomEvent 是用来创建自定义事件的构造函数,它可以创建一个自定义事件对象,该对象可以包含任意的数据,用于在 DOM 中传递信息(PS:所有传递的数据都要放在 details 对象里)

与原生事件不同,自定义事件可以自定义事件类型、是否冒泡、是否可以取消等信息

这里不做详细说明

CustomEvent - Web API 接口参考 | MDNCustomEvent 接口表示由程序出于某个目的而创建的事件。icon-default.png?t=N7T8https://developer.mozilla.org/zh-CN/docs/Web/API/CustomEvent

2.2 实践出真知

2.2.1 初步实现按钮

我们先来搭建一个最基本的按钮,基本步骤如下:

  • 创建 customElements template 模板 —— LC_BUTTON_CONTENT,该模板包含样式、结构
  • 新建一个继承自 HTMLElement 的类 —— LcButton,此类会被用于创建 customElements
  • 在 LcButton 中,动态创建 template 标签,并填充内容 LC_BUTTON_CONTENT
  • 通过 Element.attachShadow() 获取 ShadowRoot 对象,通过它把 Shadow DOM 追加到 template 上
  • 在页面中像使用 div 一样,使用 lc-button 标签
    <script>// 模板内容const LC_BUTTON_CONTENT = `<style>.button-container {/* background: yellow; */}.button-inner {display: inline-block;padding: 12px 20px;background-color: red;border-radius: 4px;border: none;font-size: 14px;color: #fff;cursor: pointer;}</style><section class="button-container"><div class="button-inner">自定义元素做的按钮</div></section>`;class LcButton extends HTMLElement {constructor() {super();// 创建 templateconst templateDOM = document.createElement("template");// 填充模板内容templateDOM.innerHTML = LC_BUTTON_CONTENT;// 通过 Element.attachShadow() 获取 ShadowRoot 对象// open shadow root:元素可以从 js 外部访问根节点// closed 拒绝从 js 外部访问关闭的 shadow root 节点const shadowRoot = this.attachShadow({ mode: "open" });// 把 Shadow DOM 附加到 template 上shadowRoot.appendChild(templateDOM.content.cloneNode(true));}}// 创建 Custom Elements(自定义元素)window.customElements.define("lc-button", LcButton);</script>

效果展示:

2.2.2 给按钮加属性-传递

该如何理解 属性 呢?

  • 类似于 vue 中的 props
  • 又或者原生 img 标签中的 src 属性
  • Custom Elements 内部也需要 监听 用户传入的属性

监听属性变化,基本步骤如下:

  • 定义 get observedAttributes() 监听属性变化 —— 比如 text
  • 定义 attributeChangedCallback,添加属性变化时进行的操作 —— 比如给 shadow dom 里的节点内容赋值
class LcButton extends HTMLElement {constructor() {super();// 创建 templateconst templateDOM = document.createElement("template");// 填充模板内容templateDOM.innerHTML = LC_BUTTON_CONTENT;// 通过 Element.attachShadow() 获取 ShadowRoot 对象// open shadow root:元素可以从 js 外部访问根节点// closed 拒绝从 js 外部访问关闭的 shadow root 节点const shadowRoot = this.attachShadow({ mode: "open" });// 把 Shadow DOM 附加到 template 上shadowRoot.appendChild(templateDOM.content.cloneNode(true));// 获取按钮文字 DOMthis.btnTextDOM = shadowRoot.querySelector(".button-inner");}render() {// 修改文字内容this.btnTextDOM.innerHTML = this.text;}// 如果需要在元素属性变化后,触发 attributeChangedCallback 回调函数,我们必须监听这个属性// 通过定义 observedAttributes() 来实现监听属性变化static get observedAttributes() {return ["text"];}// 当自定义元素的 属性 变化(增加、移除、更改)时调用attributeChangedCallback(name, oldVal, newVal) {this[name] = newVal;this.render();}
}

效果展示:

2.2.3 给按钮加属性-映射

传递属性,使用的方式是

<lc-button text="给按钮加属性"></lc-button>

映射属性,使用的方式是

const element = document.querySelector("lc-button");
element.text = "给按钮加属性-映射";

所谓传递属性,就是通过 attributeChangedCallback 来监听用户传入的 text,监听到变化后,开发者需要手动给 this.text 赋值

        // 当自定义元素的 属性 变化(增加、移除、更改)时调用attributeChangedCallback(name, oldVal, newVal) {this[name] = newVal;this.render();}

所谓映射属性,就是用户每次修改 text,都会自动通过 get()/set() 函数来 获取/设置 this.text,开发者不需要手动给 this.text 赋值了

        get text() {return this.getAttribute("text");}set text(value) {this.setAttribute("text", value);}// 当自定义元素的 属性 变化(增加、移除、更改)时调用attributeChangedCallback(name, oldVal, newVal) {// this[name] = newVal;this.render();}

如果强行赋值,会导致栈溢出 因为:

  • 在 attributeChangedCallback 中的语句 this.text = newVal 会自动调用 get()/set() 函数
  • get()/set() 函数函数执行会导致 this.text 发生变化
  • this.text 发生变化又会让 attributeChangedCallback 被调用
  • 如此循环往复

2.2.4 给按钮加事件-普通事件

在自定义元素 内部 加事件监听,并把自定义元素 内部值 抛出去

      class LcButton extends HTMLElement {constructor() {// 获取按钮文字 DOMthis.btnTextDOM = shadowRoot.querySelector(".button-inner");// 在自定义组件 内部 添加事件this.btnTextDOM.addEventListener("click", () => {console.log("在自定义组件 内部 添加事件");// 把内部值传出去this.onClick("把内部值传出去", 666);});}}

在自定义元素 外部 加事件监听,并接收自定义组件 内部传出来的值

      const element = document.querySelector("lc-button");// 在自定义组件 外部 添加事件element.addEventListener("click", () => {console.log("在自定义组件 外部 添加事件");});// 接收自定义组件 内部 传出来的值element.onClick = (value1, value2) => {console.log('接收内部传出来的值', value1, value2);};

2.2.5 给按钮加事件-自定义事件

所谓自定义事件,就是自定义组件内部开发者定义的、格式为 onXXX 格式的事件,在自定义组件外部可以通过 addEventListener 监听到

举个例子,下面的 onCustomClick 就是自定义事件

// 接收 自定义组件 内部的 自定义事件 传出来的值
element.addEventListener("onCustomClick", (val) => {console.log("接收 自定义组件 内部的 自定义事件 传出来的值\n", val);
});

在自定义组件内部,可以通过 dispatchEvent() 和 new CustomEvent() 来添加自定义事件

这里为了方便触发,我规定了在点击时,发出这个 onCustomClick 事件

// 在自定义组件 内部 添加事件
this.btnTextDOM.addEventListener("click", () => {// dispatchEvent() 方法会向一个指定的 事件目标 派发一个 Eventthis.dispatchEvent(// new CustomEvent() 方法创建一个新的 CustomEvent 对象new CustomEvent("onCustomClick", {// 需要把想要传递的参数包裹在一个包含detail属性的对象,否则传递的参数不会被挂载detail: {keysone: "onCustomClick 自定义事件抛出的值",keystwo: Number(new Date()),},}));
});

效果如下:

通过上面一系列精彩的操作,可以看出手写一个 Web Component 组件,还是比较费劲的

但没关系,后面会介绍更简单的方式,开发 Wep Components 组件,敬请期待!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/122153.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 2558. 从数量最多的堆取走礼物

【LetMeFly】2558.从数量最多的堆取走礼物 力扣题目链接&#xff1a;https://leetcode.cn/problems/take-gifts-from-the-richest-pile/ 给你一个整数数组 gifts &#xff0c;表示各堆礼物的数量。每一秒&#xff0c;你需要执行以下操作&#xff1a; 选择礼物数量最多的那一…

Go 并发编程

文章目录 用 goroutine 和通道实现并发用 sync 实现并发互斥锁sync.Once 结构体同步等待组 zync.WaitGroup竞态检测器 应用自增整数生成器并发消息发送器多路复合计算器用 select 关键字创建多通道监听器多路复合计算器超时处理 用无缓冲通道阻塞主线程用筛法求素数创建随机数生…

Spring Security漏洞防护—HttpFirewall和 HTTPS

一、HttpFirewall Spring Security有几个领域&#xff0c;你所定义的 pattern 会针对传入的请求进行测试&#xff0c;以决定应该如何处理请求。这发生在 FilterChainProxy 决定请求应该通过哪个过滤链时&#xff0c;以及 FilterSecurityInterceptor 决定哪些安全约束适用于请求…

阿里云/腾讯云国际站代理:国际腾讯云的优势

国际腾讯云具有以下优势&#xff1a; 1. 全球覆盖&#xff1a;腾讯云在全球拥有30个区域&#xff0c;覆盖6个大洲&#xff0c;能够提供全球范围的云服务&#xff0c;满足不同地区用户的需求。 2. 大规模网络&#xff1a;腾讯云拥有庞大的全球网络&#xff0c;包括多个高速骨干…

Go 语言操作 MongoDb

文章目录 连接数据库插入数据库插入一条数据批量插入数据 查询数据用 BSON 进行复合查询聚合查询 更新数据删除数据 连接数据库 package mainimport ("context""go.mongodb.org/mongo-driver/mongo""go.mongodb.org/mongo-driver/mongo/options"…

计算机毕设 flink大数据淘宝用户行为数据实时分析与可视化

文章目录 0 前言1、环境准备1.1 flink 下载相关 jar 包1.2 生成 kafka 数据1.3 开发前的三个小 tip 2、flink-sql 客户端编写运行 sql2.1 创建 kafka 数据源表2.2 指标统计&#xff1a;每小时成交量2.2.1 创建 es 结果表&#xff0c; 存放每小时的成交量2.2.2 执行 sql &#x…

Servlet 与Spring对比!

前言&#xff1a; Spring相关的框架知识&#xff0c;算是目前公司在用的前沿知识了&#xff0c;很重要&#xff01;&#xff01; 那么以Spring为基础的框架有几个&#xff1f; 以Spring为基础的框架包括若干模块&#xff0c;其中主要的有Spring Framework、Spring Boot、Spring…

十七、模型构建器(ModelBuilder)快速提取城市建成区——批量将夜光数据投影、转为整型(基于参考比较法)

一、前言 本文以参考比较法提取城市建成区为例,详细介绍如何使用模型构建器(ModelBuilder)提高效率,特别说明一下,模型构建器构建的模型是可以保存成为工具,日后需要使用的时候可以直接使用工具不用重复构建,因为模型构建器构建的工作流如果不保存,下次就不能使用,需…

Mybatis基础

文章目录 Mybatis基础XML语言概述使用Mybatis配置Mybatis增删改查复杂查询事务操作动态 SQLifchoose、when、otherwise 缓存机制注解开发 Mybatis基础 虽然我们能够通过JDBC来连接和操作数据库&#xff0c;但是哪怕只是完成一个SQL语句的执行&#xff0c;都需要编写大量的代码…

【Python入门教程】基于OpenCV视频分解成图片+图片组合成视频(视频抽帧组帧)

在人工智能爆火的今天&#xff0c;深度学习被广泛应用于各个领域。深度学习的模型训练离不开大量的样本库。我之前分享过【Python爬虫】批量爬取网页的图片&制作数据集&#xff0c;今天跟大家分享一下如何使用OpenCV库对视频进行抽帧&#xff0c;从而增加样本图片的数量。正…

Operator开发之operator-sdk入门

1 operator-sdk 除了kubebuilder&#xff0c;operator-sdk是另一个常用的用于开发Operator的框架&#xff0c;不过operator-sdk还是基于kubebuilder&#xff0c;因此&#xff0c;通常还是建议使用kubebuilder开发Operator。 2 环境准备 跟kubebuilder类似&#xff0c;需要安…

Tensorflow2 中模型训练标签顺序和预测结果标签顺序不一致问题解决办法

本篇文章将详细介绍Tensorflow2.x中模型训练标签顺序和预测结果标签顺序不一致问题&#xff0c;这个问题如果考虑不周&#xff0c;或者标签顺序没有控制好的情况下会出现预测结果精度极其不准确的情况。 训练数据集的结构&#xff1a;数据集有超过10的类别数&#xff0c;这里包…

【Java 进阶篇】Java HTTP 请求消息详解

HTTP&#xff08;Hypertext Transfer Protocol&#xff09;是一种用于传输超文本的应用层协议&#xff0c;广泛用于构建互联网应用。在Java中&#xff0c;我们经常需要发送HTTP请求来与远程服务器进行通信。本文将详细介绍Java中HTTP请求消息的各个部分&#xff0c;包括请求行、…

shouldComponentUpdate 是做什么的?

目录 前言 生命周期函数 shouldComponentUpdate 的写法和用法 代码 事件和API 优缺点 方法 总结 理论 结论 shouldComponentUpdate 是 React 类组件中的一个生命周期方法&#xff0c;用于决定一个组件的 props 或 state 发生变化时是否应该重新渲染。默认情况下&…

什么是离岸金融 (OFFSHORE FINANCE)

什么是离岸金融 离岸金融&#xff08;Offshore Finance&#xff09;是指在国外或离岸地区开设金融账户、进行金融交易或管理金融资产的金融实践。这通常涉及将资金、投资、银行账户或金融交易放置在国外的特殊地区或国家&#xff0c;以获得各种金融和税收优惠。离岸金融的目的…

设计模式--7个原则

单一职责原则&#xff1a;一个类负责一项职责。 里氏替换原则&#xff1a;继承与派生的规则。 依赖倒置原则&#xff1a;高层模块不应该依赖基层模块&#xff0c;二者都应该依赖其抽象&#xff1b;抽象不应该依赖细节&#xff1b;细节应该依赖抽象。即针对接口编程&#xff0…

Linux系统下DHCP服务安装部署和使用实例详解(蜜罐)

目录 一、概述 二、具体配置如下&#xff1a; 一、概述 DHCP &#xff1a;动态主机设置协议&#xff08;英语&#xff1a;Dynamic Host Configuration Protocol&#xff0c;DHCP&#xff09;是一个局域网的网络协议&#xff0c;使用UDP协议工作&#xff0c;主要有两个用途&…

公司电脑如何限制安装软件

公司电脑如何限制安装软件 安企神终端管理系统下载使用 在企业环境中&#xff0c;电脑已经成为企业中必不可少的办公工具&#xff0c;确保员工的生产力和公司的信息安全是至关重要的。为了实现这一目标&#xff0c;公司可能会限制员工在某些情况下安装软件或者由管理员来为终…

可以实时监控屏幕的电脑监控软件

电脑已经成为了人们工作和生活不可或缺的工具。然而&#xff0c;这也带来了诸多安全问题。一些人可能会利用电脑进行不恰当的操作&#xff0c;如聊天、游戏、观看视频等&#xff0c;甚至会泄露公司的商业机密。 电脑监控软件的定义 电脑监控软件是一种用于监控电脑使用情况的软…

【Docker】Docker的网络

Docker提供了多种内置的网络模式&#xff0c;用于在容器之间建立网络连接。这些网络模式&#xff0c;包括桥接网络、主机网络、无网络模式。我们将主要探讨每种网络模式的优缺点、适用场景。 桥接网络 桥接网络是Docker的默认网络模式。在桥接网络中&#xff0c;Docker会为每…