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

news2025/1/9 1:42:20

目录

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() {
  // ...
  // 获取按钮文字 DOM
  this.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();
          // 创建 template
          const 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();
    // 创建 template
    const 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));
    // 获取按钮文字 DOM
    this.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() {
          // 获取按钮文字 DOM
          this.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() 方法会向一个指定的 事件目标 派发一个 Event
  this.dispatchEvent(
    // new CustomEvent() 方法创建一个新的 CustomEvent 对象
    new CustomEvent("onCustomClick", {
      // 需要把想要传递的参数包裹在一个包含detail属性的对象,否则传递的参数不会被挂载
      detail: {
        keysone: "onCustomClick 自定义事件抛出的值",
        keystwo: Number(new Date()),
      },
    })
  );
});

效果如下:

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

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

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

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

相关文章

绝对路径与相对路径

目录 一、绝对路径 二、相对路径 三、举例 一、绝对路径 绝对路径是指从根目录开始的完整路径&#xff0c;包括所有父目录的路径&#xff0c;直到目标文件或者目录 所在的位置。 全文件名全路径文件名绝对路经完整的路径 例如:在windows系统中&#xff0c;绝对路径通常以…

nuxt脚手架

nuxt(vue)和next(react)都是服务端渲染的 安装 - NuxtJS | Nuxt.js 中文网 首先要安装 create-nuxt-app: npm install create-nuxt-app -g 然后&#xff1a;npx create-nuxt-app master 建议在cmd中操作&#xff0c;可以单选和多选 然后就各种单选和多选&#xff0c;多选按…

实用篇-服务拆分及远程调用

一、服务拆分 服务拆分(也叫项目拆分) 注意事项&#xff1a; 不同的微服务&#xff0c;不要重复开发相同的业务要求微服务之间数据独立&#xff0c;不要访问其他微服务的数据库微服务可用将自己的业务暴漏为接口&#xff0c;供其他服务调用 1. Cloud-Demo拆分 首先有一个已…

项目管理之如何有效定义项目目标

项目目标管理是项目管理中非常重要的一个环节&#xff0c;它可以帮助项目团队明确目标&#xff0c;制定合理可行的计划&#xff0c;确保项目顺利实施并取得成功。在定义项目目标时&#xff0c;需要遵循SMART原则&#xff0c;确保目标具体、明确、可衡量、可实现、相关且有时间和…

【Reticulate Micro】申请1000万美元纳斯达克IPO上市

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于美国的【Reticulate Micro】近期已向美国证券交易委员会&#xff08;SEC&#xff09;提交招股书&#xff0c;申请在纳斯达克IPO上市&#xff0c;股票代码为(RMIC) &#xff0c;Reticulate…

TensorFlow学习:使用官方模型和自己的训练数据进行图片分类

前言 教程来源&#xff1a;清华大佬重讲机器视觉&#xff01;TensorFlowOpencv&#xff1a;深度学习机器视觉图像处理实战教程&#xff0c;物体检测/缺陷检测/图像识别 注&#xff1a; 这个教程与官网教程有些区别&#xff0c;教程里的api比较旧&#xff0c;核心思想是没有变…

C++——类和对象(了解面向过程和面向对象、初步认识类和对象、类大小的计算、this指针)

类和对象 文章目录 类和对象1. 面向过程和面向对象1.1 面向过程1.2 面向对象 2. 类和对象2.1 什么是类2.2 类的定义2.2.1 声明和定义类中函数的两种方法2.2.2 声明成员变量的小细节 2.3 访问限定符2.3.1 访问限定符的作用范围2.3.2 class类和struct类的默认访问权限 2.4 类的实…

手部关键点检测5:C++实现手部关键点检测(手部姿势估计)含源码 可实时检测

手部关键点检测5&#xff1a;C实现手部关键点检测(手部姿势估计)含源码 可实时检测 目录 手部关键点检测4&#xff1a;C实现手部关键点检测(手部姿势估计)含源码 可实时检测 1.项目介绍 2.手部关键点检测(手部姿势估计)方法 (1)Top-Down(自上而下)方法 (2)Bottom-Up(自下…

mac vscode 使用 clangd

C 的智能提示 IntelliSense 非常不准&#xff0c;我们可以使用 clangd clangd 缺点就是配置繁琐&#xff0c;优点就是跳转和提示代码精准 开启 clangd 之后会提示你关闭 IntelliSense 1、安装插件 clangd 搜索第一个下载多的就是 2、配置 clangd 可执行程序路径 clangd 插…

【虹科干货】谈谈Redis Enterprise的实时搜索

我们都知道&#xff0c;用户在使用应用程序时候&#xff0c;对于速度有着越来越高的要求&#xff0c;真可谓是“一秒也等不及”。而开发团队又该怎样来满足这种对于实时性的期望呢&#xff1f; 文章速览&#xff1a; Redis Enterprise实时搜索的应用场景利用索引为开发人员带…

特殊类设计[下] --- 单例模式

文章目录 5.只能创建一个对象的类5.1设计模式[2.5 万字详解&#xff1a;23 种设计模式](https://zhuanlan.zhihu.com/p/433152245)5.2单例模式1.饿汉模式1.懒汉模式 6.饿汉模式7.懒汉模式7.1饿汉模式优缺点:7.2懒汉模式1.线程安全问题2.单例对象的析构问题 8.整体代码9.C11后可…

C++项目——云备份-⑤-数据管理模块的设计与实现

文章目录 专栏导读1.要管理的数据有哪些2.如何管理数据3.数据信息结构体设计与实现4.数据管理类设计5.数据管理类实现6.数据管理模块整理 专栏导读 &#x1f338;作者简介&#xff1a;花想云 &#xff0c;在读本科生一枚&#xff0c;C/C领域新星创作者&#xff0c;新星计划导师…

打破尺寸记录!荷兰QuTech研发16量子点阵列新技术

承载16个量子点交叉条阵列的量子芯片&#xff0c;可无缝集成到棋盘图案&#xff08;图片来源&#xff1a;网络&#xff09; 由荷兰代尔夫特理工大学(TU Delft)和荷兰应用科学研究组织(TNO)组建的荷兰量子计算研究中心QuTech的研究人员开发了一种用相对较少的控制线来控制大量量…

【QT】Qt控件不显示图标

问题描述 本人在跟着B站视频学习QT时&#xff0c;遇到了一件十分悲惨的事情&#xff0c;一模一样的步骤&#xff0c;我的图标却不能显示。 于是我上网查询一下解决方案&#xff0c;第一种&#xff0c;亲测没用&#xff1b;第二种亲测可以。 解决方法一 1、构建 -> 清理项目…

实战CubeMX配置CAN通讯教程,避免踩坑,cubeMX 回环模式可以但正常模式无法通信

文章目录 实战CubeMX配置CAN通讯教程&#xff0c;避免踩坑&#xff0c;cubeMX 回环模式可以但正常模式无法通信1. 先配置两个LED等的普通IO口&#xff0c;作为通信指示信号2.配置时钟单元3.配置工程文件4.配置代码生成的参数5.配置CAN通信的波特率&#xff0c;注意如果配置成50…

成都瀚网科技有限公司:抖音小店收益计算大揭秘,一招提升你的利润!

你是否曾对抖音小店的收益计算方式感到困惑&#xff1f;想要了解如何提高抖音小店的收益吗&#xff1f;本文将为你揭开抖音小店收益计算的神秘面纱&#xff0c;并分享一些实用的提升利润的方法。 一、抖音小店收益计算方式 抖音小店的收益主要来自于商品销售收入、佣金收入以及…

0146 网络层

目录 4 网络层 4.1 网络层的功能 4.2 路由算法与路由协议 4.3 IPv4 4.4 IPv6 4 网络层 4.1 网络层的功能 4.2 路由算法与路由协议 4.3 IPv4 4.4 IPv6 部分习题 1.网络层的主要目的是&#xff08;&#xff09; A.在邻接结点间进行数据报传输 B.在邻接结点间进行数…

郑州职工注意!郑州市职工数字人才技能竞赛正式启动

10月26日&#xff0c;由郑州市劳动竞赛委员会办公室、郑州市总工会、郑州市大数据管理局、郑州市人力资源和社会保障局、郑州市科学技术局主办&#xff0c;郑东新区总工会、中科大数据研究院联合承办的郑州市职工数字人才技能竞赛在郑东新区顺利举行启动仪式。 河南省总工会副主…

『进阶之路』- 揭开ThreadLocal神秘面纱

阅读本文主要可以解决以下困惑&#xff1a; 什么是ThreadLocal&#xff0c;隔离线程的本地变量ThreadLocal的数据结构是怎么样的&#xff0c;为什么能实现线程隔离ThreadLocal的get和set方法ThreadLocal如何实现的线程安全&#xff1f;结合同步锁机制&#xff0c;空间换取时间…

2023年腾讯云双11服务器活动及价格表

双十一购物狂欢节即将到来&#xff0c;腾讯云作为国内领先的云计算服务提供商&#xff0c;推出了一系列优惠活动&#xff0c;下面给大家详细介绍腾讯云双11服务器活动及价格表。 一、腾讯云双11活动入口 活动入口&#xff1a;txy.ink/1111/ 二、腾讯云双11活动时间 即日起至…