『Vue组件』Web Components 原生组件化

news2024/12/26 23:33:48

 在学习Vue插槽(Slots)之前,我们可以先对Web Components的概念进行一个了解。因为Vue的模板系统,组件实现以及插槽相关的实现是参考于Web Components标准进行的上层设计。

 在Vue中通过自定义组件、自定义标签以及自定义属性配合使用实现了Vue的组件化,是通过webpack/vite等工程化工具编译过后才能在浏览器上进行解析、渲染。

 而对于HTML/DOM本身是具有一套组件化写法(规范),也就是本文的重点:Web Component,通过HTML/DOM本身的组件化,我们可以直接通过自定义标签、属性成功渲染,而不需要工程化解析组件或标签。

一、缘起

 Web Components 旨在解决这些问题 — 它由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。由于在HTML/DOM中重用HTML结构的不便,推出了Web Components规范,最终成为HTML/DOM原生组件化的标准。

 希望该方案提供给开发者能够自定义可重用的、可被浏览器正常解析的标签,让逻辑样式被封装在一个组件中,最终使用自定标签的方式渲染。

二、使用

 在原生组件化中有这么三个关键的内容(具体内容见上文Web Components MDN):

  1. Html Template(HTML模板): <template> 和 <slot> 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
  2. customElement(自定义元素):一组 JavaScript API,允许您定义 custom elements 及其行为,然后可以在您的用户界面中按照需要使用它们。
  3. shadowDom(影子DOM):一组 JavaScript API,用于将封装的“影子”DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。

1. 方法介绍

  • customElements.define():方法用来注册一个 custom element,该方法接受以下参数:
    • 表示所创建的元素名称的符合 DOMString 标准的字符串。注意,custom element 的名称不能是单个单词,且其中必须要有短横线。
    • 用于定义元素行为的 类 。
    • 可选参数,一个包含 extends 属性的配置对象,是可选参数。它指定了所创建的元素继承自哪个内置元素,可以继承任何内置元素。
// 以下通过hello-word自定义标签示范define方法的使用
// 元素行为类需要继承于HTML元素类,可以是父类HTMLElement,也可以是子类如HTMLParagraphElement
class HelloWord extends HTMLElement {
  constructor() {
    // 需要调用父类的构造器, 此处的this指向的是html中的自定义标签元素
    super();
  }
}
  • Element.attachShadow():方法给指定的元素挂载一个 Shadow DOM,并且返回对 ShadowRoot 的引用。

2. 用户信息Demo

 场景:我们希望在多个页面上使用到用户信息的展示,考虑到代码的重复性、可维护性等综合因素,决定使用原生组件化来实现用户信息展示的组件。

 方案:通过使用customElements.define定义自定义组件、属性以及通过元素行为类中的创建DOM元素方法创建对应的组件,最后在页面中使用自定义标签的方式渲染对应的用户信息组件。

  1. 在页面上配置自定义标签及属性,类似于Vue中组件的写法:
<user-info
  name="Donpe1"
  avatar="https://avatars.githubusercontent.com/u/1004691?v=4"
  age="22"
  occupation="student"
  >
  Web Components Information
</user-info>

 但仅仅是在HTML中使用自定义标签是无效的,新版的Google浏览器中会将自定义标签认作为未知HTML标签并按照HTML元素的解析流程解析其内容及子元素。也就是说,在页面上当前仅只显示元素内容Web Components Information

  1. 配置UserInfo行为类,为UserInfo行为类配置构造函数、创建元素的方法:
class UserInfo extends HTMLElement {
  constructor() {
    super();
    this.name = this.getAttribute('name');
    this.avatar = this.getAttribute('avatar');
    this.age = this.getAttribute('age');
    this.occupation = this.getAttribute('occupation');
    this.information = this.textContent;
    this.init();
  }

  init () {
    const shadowRoot = this.attachShadow({ mode: 'open' });
    shadowRoot.appendChild(this.createDom());
  }

  /**
         * @description: createDom 实例化组件DOM
         */
  createDom() {
    const oContainer = this.createContainer();
    oContainer.appendChild(this.createName());
    oContainer.appendChild(this.createAvatar());
    oContainer.appendChild(this.createAge());
    oContainer.appendChild(this.createOccupation());
    oContainer.appendChild(this.createContent());
    return oContainer;
  }

  createContainer() {
    const oContainer = document.createElement('div');
    oContainer.className = 'user-info-container';
    return oContainer;
  }

  createName() {
    const oName = document.createElement('p');
    oName.className = 'user-info-name';
    oName.textContent = `Name: ${this.name}`;
    return oName;
  }

  createAvatar() {
    const oAvatar = document.createElement('div');
    oAvatar.className = 'user-info-avatar';
    oAvatar.innerHTML = `<img style="width: 100px;" src="${this.avatar}" />`
    return oAvatar;
  }

  createAge() {
    const oAge = document.createElement('p');
    oAge.className = 'user-info-age';
    oAge.textContent = `Age: ${this.age}`;
    return oAge;
  }

  createOccupation() {
    const oOccupation = document.createElement('p');
    oOccupation.className = 'user-info-occupation';
    oOccupation.textContent = `Occupation: ${this.occupation}`;
    return oOccupation;
  }

  createContent() {
    const oContent = document.createElement('h1');
    oContent.className = 'user-info-content';
    oContent.textContent = this.information;
    return oContent;
  }
}

 其中的重点并非是各个create函数,而是初始化函数中的attachShadow方法。因为我们需要将自定义的元素结构挂载至shadowRoot上。这么做的原因是封装、隔离Web组件的样式和功能,不会受到外部样式的污染。如果有需要,我们可以在元素行为类中给shadowRoot添加style标签,在其中填写组件内部需要隔离的样式:

const style = document.createElement('style');
style.textContent = `
  button {
    background-color: blue;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
  }
`;

// 将样式添加到Shadow DOM中
shadowRoot.appendChild(style);

 当然,上述的创建元素方法可以通过ES6中的模板字符串(字面量)进行替换,这里不做演示。做完上述的元素行为类的编写,还需要对其进行注册。

  1. 注册自定义组件

 这里便需要使用到上述提到的customElements.define方法对自定义的标签进行注册:

window.customElements.define('user-info', UserInfo);
  1. 效果图

 当完成了上述三个步骤后我们便完成了UserInfo组件的注册,也就能得到目标效果:

 同时,通过控制台的结构我们可以清晰的看到其中的shadowRoot对组件结构与DOM进行了隔离,如下:

 在控制台结构中仍然是以自定义标签的形式进行展现,不同的是多了shadow-root这一元素,并且在其中存放着页面真实展示的结构。

3. template、slot使用

 在原生组件开发中同样还有另外一种写法,也就是Template与Slot配合的写法,这种写法非常类似于Vue中的插槽写法。不知道大家是否使用过script作为模板进行模板替换的复用开发经历,这种写法就是在此基础上使用slot标签进行占位。对该标签进行组件注册之后,便能够在HTML结构中进行复用。

  1. template模板编写
<template id="my-article-template">
  <style>
    h1 {
      color: red;
    }

    h1 .author,
    h1 .date-time {
      font-size: 16px;
      color: #666;
      font-weight: normal;
    }
  </style>
  <div class="my-article">
    <h1 class="my-article-title">
      <slot name="title" class="title"></slot>
      <slot name="author" class="author"></slot>
      <slot name="dateTime" class="date-time"></slot>
    </h1>
    <p class="my-article-content">
      <slot name="content"></slot>
    </p>
  </div>
</template>
  1. template组件注册

 本质上与上一小节的元素添加至shadowRoot中是一样的,不过不同的是我们通过HTMLTemplateElement的content方法得到了一个DocumentFragment元素,并通过cloneNode的方式克隆之后追加至shadowRoot元素中。

 至于为何要使用到cloneNode,是为了防止多个自定义标签共用相同的结构实例,也是避免某处的修改影响到多个组件,通过克隆使得各个Article组件都是相互独立的。

class MyArticle extends HTMLElement {
  constructor() {
    super();
    const _tpl = document.getElementById('my-article-template').content;
    const shadowRoot = this.attachShadow({ mode: 'open' });
    shadowRoot.appendChild(_tpl.cloneNode(true));
  }
}
window.customElements.define('my-article', MyArticle)
  1. 自定义组件的使用
<my-article>
  <p slot="title">This is my Title</p>
  <span slot="author">Donpe1</span>
  <span slot="dateTime"> - 2023-01-04 16:07:48</span>
  <p slot="content">This is my Content</p>
</my-article>
  1. 效果图:

 可以从效果图中发现,不仅是Slot插槽成功渲染了,并且样式也得到了渲染。有兴趣的朋友可以自行验证shadowRoot对于组件样式与外界样式的隔离。

三、总结

 本文从Web Components的缘起入手,简单描述了Web Components以及其中关键的几个方法。并且通过两个Demo案例演示了原生组件化应该如何编写,以及原生组件化中的template、slot机制。同时对于shadowDom、shadowRoot大家应该也有了简单的认知,更多相关的内容在后续有机会会与大家分享。

 对于Web Components并不是必须掌握的内容,毕竟很难落地,不过并不代表可以不知道它的存在。同时知道了原生组件化的开发,也将对我们学习Vue组件化开发、Vue插槽机制起到一定的帮助。

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

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

相关文章

c++注入dll调用call

1.先写个我们要调用的函数 #include<iostream> using namespace std; void test(int a, int& b) { cout << a << b << endl; b a b; }void main() { int a 2; int b0; test(a, b); cout << b << endl; system("pause");…

再鼎医药面临严重的监管和产品竞争风险

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 再鼎医药&#xff08;ZLAB&#xff09;是一家生物制药公司&#xff0c;致力于开发和商业化癌症和传染病的创新疗法。该公司的产品组合包括用于治疗卵巢癌的靶向药Zejula&#xff0c;用于治疗脑癌的Optune&#xff0c;用于治…

Eplan2022 复制已有的宏文件生成新的原理图宏文件

下图所示为wago的787-722稳压电源&#xff0c;我们可以从官网下载到相应的eplan宏文件并导入数据源库。但是能下载到eplan宏文件的只是少部分公司的部件。那么没有宏文件的部件该怎么办&#xff1f; 接下来以明纬开关电源 NDR-120-24为例&#xff0c;创建一个宏文件。选择【主数…

linux密码忘了?一招解决

目录 一、前言 二、进入编辑界面 三、单用户模式 四、修改密码 五、更新信息 六、退出 七、验证 一、前言 版本&#xff1a;centos7.9、VMware15.5 在我们学习linux运行级别的时候&#xff0c;面试题可能会出如何找回root密码&#xff0c;下面来详细的介绍一波&#xff…

Mysql索引+事务+存储引擎

索引 索引的概念 索引是一个排序的列表&#xff0c;在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址&#xff08;类似于C语言的链表通过指针指向数据记录的内存地址&#xff09;。 使用索引后可以不用扫描全表来定位某行的数据&#xff0c;而是先通过索引表找…

day29_jdbc

今日内容 零、 复习昨日 一、JDBC 二、登录 三、ORM 零、 复习昨日 DDL (针对结构,库,表,列,索引) 最重要建表语句 create table emp(empno int primary key auto_increment,ename varchar(10) not null,sal double(10,2) default 3000,hiredate date unique )DML 增删改 inser…

TIA博途中FC和FB的区别与应用

TIA博途中FC和FB的区别与应用 从程序运行过程的角度看,FC、FB的区别在于传递参数方式不同: FC:通过L堆栈区传递参数 FB:通过背景DB传递参数 FC与FB具有不同的特性:  FC自身不能存储过程状态  FB可以通过静态变量存储中间过程状态  FB可以包含调用其他的FB及其背景D…

【Go编程语言】流程控制

流程控制 文章目录 流程控制一、if 语句1.if 嵌套语句 二、switch 语句三、for 循环四、string 程序的流程控制结构一具有三种&#xff1a;顺序结构&#xff0c;选择结构&#xff0c;循环结构 顺序结构&#xff1a;从上到下&#xff0c;逐行执行。默认的逻辑 选择结构&#xf…

C4D云渲染平台哪家好?

Cinema 4d&#xff08;简称C4D&#xff09;作为CG业内一款人人皆知的三维制作软件&#xff0c;操作界面简单清爽是它的亮点之一。此外&#xff0c;具有建模、灯光、材质、绑定、动画、渲染等强大功能的C4D的应用范围也十分广泛&#xff0c;包括电商海报、时尚设计、影视后期、广…

Java系统环境变量配置

PATH环境变量 PATH环境变量用于保存一系列命令&#xff08;可执行程序&#xff09;的路径&#xff0c;每个路径之间以分号分隔。当在命令行窗口运行一个命令时&#xff0c;操作系统首先会在当前目录下查找是否存在该命令对应的可执行文件&#xff0c;如果未找到&#xff0c;操作…

最新一键将网站DEDECMS 迁移到 WordPress脚本

DEDECMS宣布开始对运营类网站收费了,一年5800元,如果不想付费,那就需要转到其他网站了,就是一个不错的选择。 最近大神wordpress果酱牛逼闪闪的Denis和几个程序员制作了一个转Wordpress的,有需要的可以去下载使用,完全免费! 经过几个夜晚通宵的努力,我们终于搞定了一键…

开发必备,开源 or 免费的 AI 编程助手

AI 大模型的火热&#xff0c;让开发圈近来如虎添翼&#xff0c;各种各样基于 AI 技术的开发者工具和新范式不断涌现&#xff0c;尤其是 Github 和 OpenAI 共同推出的 Copilot X &#xff0c;更是一骑绝尘。本文推荐一些开源 or 免费的 AI 编程工具&#xff0c;不妨试着用起来。…

基于stm32物联网开发板(1)

基于stm32物联网开发板(1) 本开发板采用了STM32F103RET6作为核心CPU&#xff0c;72MHZ工作频率&#xff0c;512KB flash&#xff0c;64KB Sram。本开发平台外设模块有ESP8266 WIFI模块、1.3寸LCD彩屏、SYN6288语音模块、MAX30102心率血氧传感器、AD8232心电图监测模块、BH1750环…

mysql的高级查询语句

1.本文前言 数据库是用来存储数据&#xff0c;更新&#xff0c;查询数据的工具&#xff0c;而查询数据是一个数据库最为核心的功能&#xff0c;数据库是用来承载信息&#xff0c;而信息是用来分析和查看的。所以掌握更为精细化的查询方式是很有必要的。本文将围绕数据的高级查…

Figma中文网?比Figma更懂你的设计网站!

一个比 Figma 更懂你的设计网站的 Figma 中文网 —— 即时设计是一个非常有用的设计资源平台&#xff0c;它提供了大量的免费设计素材&#xff0c;包括来自各大厂商的 UI 组件库、精美的模板、插画设计和矢量图标素材等等。设计师可以从中学习到大师的设计技巧和规范&#xff0…

Ubuntu18.04 下安装 MATLAB 2021a

1、MATLAB 软件获取 南开大学软件之家&#xff1a;http://ca.nankai.edu.cn&#xff0c;非南开大学校园IP无法登陆该平台。 点击浏览更多&#xff0c;找到 R2021a_Linux&#xff0c;开始下载&#xff0c;下载 R2021a_Linux.iso 镜像文件。 参考该网页下的个人版在线安装指南&a…

基于git的开发规范总结

文章目录 各分支命名规范gitee基本开发流程及定义gitflow工作流gitflow工作流常用分支主要工作流程命名规则gitflow工作流程图 Git分支开发管理策略主分支Master开发分支DevelopGit创建Develop分支的命令&#xff1a;将Develop分支发布到Master分支的命令&#xff1a; 临时性分…

【windows编程之对话框】对话框原理,对话框的创建

文章目录 引言一.对话框原理1.对话框的分类2.对话框的基本使用2.自定义对话框窗口消息处理函数 二.模式对话框- 1.创建对话框- 2.对话框的关闭- 3.对话框消息 三.模式对话框创建过程实践四.无模式对话框 引言 在本章节中我们来讲解Windows/Win32编程中对话框的原理和对话框的创…

Ajax请求,基于JSON的数据交换 实例

前端代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title>发送Ajax GET请求 展示学生信息列表</title> <script type"text/javascript"> w…

camunda任务监听器如何使用

在Camunda工作流引擎中&#xff0c;任务监听器是一种机制&#xff0c;用于在业务任务执行期间捕获特定事件并执行相应的操作。它们可以帮助您实现一些重要的任务&#xff0c;例如&#xff1a; 1、记录或更新业务数据&#xff1a;当任务完成或取消时&#xff0c;您可以使用任务…