现在的前端开发基本离不开 React、Vue 这两个框架的支撑,而这两个框架下面又衍生出了许多的自定义组件库:

  • Element(Vue)

  • Ant Design(React)

这些组件库的出现,让我们可以直接使用已经封装好的组件,而且在开源社区的帮助下,出现了很多的模板项目( vue-element-admin、Ant Design Pro ),能让我们快速的开始一个项目。

虽然 React、Vue 为我们的组件开发提供了便利,但是这两者在组件的开发思路上,一个是自创的 JSX 语法,一个是特有的单文件模板的语法,两者的目标都是想提供一种组件的封装方法。毕竟都有其原创的东西在里面,和我们刚开始接触的 Web 基础的 HTML、CSS、JS 的方式还是有些出入的。今天介绍的就是,通过 HTML、CSS、JS 的方式来实现自定义的组件,也是目前浏览器原生提供的方案:Web Components。

什么是 Web Components?

Web Components 本身不是一个单独的规范,而是由一组DOM API 和 HTML 规范所组成,用于创建可复用的自定义名字的 HTML 标签,并且可以直接在你的 Web 应用中使用。

代码的复用一直都是我们追求的目标,在 JS 中可复用的代码我们可以封装成一个函数,但是对于复杂的HTML(包括相关的样式及交互逻辑),我们一直都没有比较好的办法来进行复用。要么借助后端的模板引擎,要么借助已有框架对 DOM API 的二次封装,而 Web Components 的出现就是为了补足浏览器在这方面的能力。

如何使用 Web Components?

Web Components 中包含的几个规范,都已在 W3C 和 HTML 标准中进行了规范化,主要由三部分组成:

  • Custom elements(自定义元素):一组 JavaScript API,用来创建自定义的 HTML标签,并允许标签创建或销毁时进行一些操作;

  • Shadow DOM(影子DOM):一组 JavaScript API,用于将创建的 DOM Tree 插入到现有的元素中,且 DOM Tree 不能被外部修改,不用担心元素被其他地方影响;

  • HTML templates(HTML模板):通过 <template><slot> 直接在 HTML 文件中编写模板,然后通过 DOM API 获取。

Custom elements(自定义元素)

浏览器提供了一个方法:customElements.define() , 来进行自定义标签的定义。该方法接受三个参数:

  • 自定义元素的名称,一个 DOMString 标准的字符串,为了防止自定义元素的冲突,必须是一个带短横线连接的名称(e.g. custom-tag)。

  • 定义自定义元素的一些行为,类似于 React、Vue 中的生命周期。

  • 扩展参数(可选),该参数类型为一个对象,且需要包含 extends 属性,用于指定创建的元素继承自哪一个内置元素(e.g. { extends: 'p' })。

下面通过一些例子,演示其用法,完整代码放到了 JS Bin 上。

创建一个新的 HTML 标签

先看看如何创建一个全新的自定义元素。

class HelloUser extends HTMLElement {constructor() {// 必须调用 super 方法super();// 创建一个 div 标签const $box = document.createElement("p");let userName = "User Name";if (this.hasAttribute("name")) {// 如果存在 name 属性,读取 name 属性的值userName = this.getAttribute("name");}// 设置 div 标签的文本内容$box.innerText = `Hello ${userName}`;// 创建一个 shadow 节点,创建的其他元素应附着在该节点上const shadow = this.attachShadow({ mode: "open" });shadow.appendChild($box);}
}// 定义一个名为 <hello-user /> 的元素
customElements.define("hello-user", HelloUser);
<hello-user name="Shenfq"></hello-user>

这时候页面上就会生成一个 <p> 标签,其文本内容为:Hello Shenfq。这种形式的自定义元素被称为:Autonomous custom elements,是一个独立的元素,可以在 HTML 中直接使用。

扩展已有的 HTML 标签

我们除了可以定义一个全新的 HTML 标签,还可以对已有的 HTML 标签进行扩展,例如,我们需要封装一个与 <ul> 标签能力类似的组件,就可以使用如下方式:

class SkillList extends HTMLUListElement {constructor() {// 必须调用 super 方法super();if (this.hasAttribute("skills") &&this.getAttribute("skills").includes(',')) {// 读取 skills 属性的值const skills = this.getAttribute("skills").split(',');skills.forEach(skill => {const item = document.createElement("li");item.innerText = skill;this.appendChild(item);})}}
}// 对 <ul> 标签进行扩展
customElements.define("skill-list", SkillList, { extends: "ul" });
<ul is="skill-list" skills="js,css,html"></ul>

对已有的标签进行扩展,需要用到 customElements.define 方法的第三个参数,且第二参数的类,也需要继承需要扩展标签的对应的类。使用的时候,只需要在标签加上 is 属性,属性值为第一个参数定义的名称。

生命周期

自定义元素的生命周期比较简单,一共只提供了四个回调方法:

  • connectedCallback:当自定义元素被插入到页面的 DOM 文档时调用。

  • disconnectedCallback:当自定义元素从 DOM 文档中被删除时调用。

  • adoptedCallback:当自定义元素被移动时调用。

  • attributeChangedCallback: 当自定义元素增加、删除、修改自身属性时调用。

下面演示一下使用方法:

class HelloUser extends HTMLElement {constructor() {// 必须调用 super 方法super();// 创建一个 div 标签const $box = document.createElement("p");let userName = "User Name";if (this.hasAttribute("name")) {// 如果存在 name 属性,读取 name 属性的值userName = this.getAttribute("name");}// 设置 div 标签的文本内容$box.innerText = `Hello ${userName}`;// 创建一个 shadow 节点,创建的其他元素应附着在该节点上const shadow = this.attachShadow({ mode: "open" });shadow.appendChild($box);}connectedCallback() {console.log('创建元素')// 5s 后移动元素到 iframesetTimeout(() => {const iframe = document.getElementsByTagName("iframe")[0]iframe.contentWindow.document.adoptNode(this)}, 5e3)}disconnectedCallback() {console.log('删除元素')}adoptedCallback() {console.log('移动元素')}
}
<!-- 页面插入一个 iframe,将自定义元素移入其中 -->
<iframe width="0" height="0"></iframe>
<hello-user name="Shenfq"></hello-user>

在元素被创建后,等待 5s,然后将自定义元素移动到 iframe 文档中,这时候能看到控制台会同时出现 删除元素移动元素 的 log。

Console

Shadow DOM(影子DOM)

在前面介绍自定义元素的时候,已经用到了 Shadow DOM。Shadow DOM 的作用是让内部的元素与外部隔离,让自定义元素的结构、样式、行为不受到外部的影响。

我们可以看到前面定义的 <hello-user> 标签,在控制台的 Elements 内,会显示一个 shadow-root ,表明内部是一个 Shadow DOM。

Shadow DOM

其实 Web Components 没有提出之前,浏览器内部就有使用 Shadow DOM 进行一些内部元素的封装,例如 <video> 标签。我们需要现在控制台的配置中,打开 Show user agent ashdow DOM 开关。

设置

然后在控制台的 Elements 内,就能看到 <video> 标签内其实也有一个 shadow-root

video 标签

创建 Shadow DOM

我们可以在任意一个节点内部创建一个 Shadow DOM,在获取元素实例后,调用 Element.attachShadow() 方法,就能将一个新的 shadow-root 附加到该元素上。

该方法接受一个对象,且只有一个 mode 属性,值为 openclosed,表示 Shadow DOM 内的节点是否能被外部获取。

<div id="root"></div>
<script>// 获取页面的const $root = document.getElementById('root');const $p = document.createElement('p');$p.innerText = '创建一个 shadow 节点';const shadow = $root.attachShadow({mode: 'open'});shadow.appendChild($p);
</script>

Shadow DOM

mode 的差异

前面提到了 mode 值为 openclosed,主要差异就是是否可以使用 Element.shadowRoot 获取到 shadow-root 进行一些操作。

<div id="root"></div>
<script>// 获取页面的const $root = document.getElementById('root');const $p = document.createElement('p');$p.innerText = '创建一个 shadow 节点';const shadow = $root.attachShadow({mode: 'open'});shadow.appendChild($p);console.log('is open', $div.shadowRoot);
</script>

open mode
<div id="root"></div>
<script>// 获取页面的const $root = document.getElementById('root');const $p = document.createElement('p');$p.innerText = '创建一个 shadow 节点';const shadow = $root.attachShadow({mode: 'closed'});shadow.appendChild($p);console.log('is closed', $div.shadowRoot);
</script>

closed mode

HTML templates(HTML模板)

前面的案例中,有个很明显的缺陷,那就是操作 DOM 还是得使用 DOM API,相比起 Vue 得模板和 React 的 JSX 效率明显更低,为了解决这个问题,在 HTML 规范中引入了 <tempate><slot> 标签。

使用模板

模板简单来说就是一个普通的 HTML 标签,可以理解成一个 div,只是这个元素内的所以内容不会展示到界面上。

<template id="helloUserTpl"><p class="name">Name</p><a target="blank" class="blog">##</a>
</template>

在 JS 中,我们可以直接通过 DOM API 获取到该模板的实例,获取到实例后,一般不能直接对模板内的元素进行修改,要调用 tpl.content.cloneNode 进行一次拷贝,因为页面上的模板并不是一次性的,可能其他的组件也要引用。

// 通过 ID 获取标签
const tplElem = document.getElementById('helloUserTpl');
const content = tplElem.content.cloneNode(true);

我们在获取到拷贝的模板后,就能对模板进行一些操作,然后再插入到 Shadow DOM 中。

<hello-user name="Shenfq" blog="http://blog.shenfq.com" /><script>class HelloUser extends HTMLElement {constructor() {// 必须调用 super 方法super();// 通过 ID 获取标签const tplElem = document.getElementById('helloUserTpl');const content = tplElem.content.cloneNode(true);if (this.hasAttribute('name')) {const $name = content.querySelector('.name');$name.innerText = this.getAttribute('name');}if (this.hasAttribute('blog')) {const $blog = content.querySelector('.blog');$blog.innerText = this.getAttribute('blog');$blog.setAttribute('href', this.getAttribute('blog'));}// 创建一个 shadow 节点,创建的其他元素应附着在该节点上const shadow = this.attachShadow({ mode: "closed" });shadow.appendChild(content);}}// 定义一个名为 <hello-user /> 的元素customElements.define("hello-user", HelloUser);
</script>

添加样式

<template> 标签中可以直接插入 <style> 标签在,模板内部定义样式。

<template id="helloUserTpl"><style>:host {display: flex;flex-direction: column;width: 200px;padding: 20px;background-color: #D4D4D4;border-radius: 3px;}.name {font-size: 20px;font-weight: 600;line-height: 1;margin: 0;margin-bottom: 5px;}.email {font-size: 12px;line-height: 1;margin: 0;margin-bottom: 15px;}</style><p class="name">User Name</p><a target="blank" class="blog">##</a>
</template>

其中 :host 伪类用来定义 shadow-root的样式,也就是包裹这个模板的标签的样式。

占位元素

占位元素就是在模板中的某个位置先占据一个位置,然后在元素插入到界面上的时候,在指定这个位置应该显示什么。

<template id="helloUserTpl"><p class="name">User Name</p><a target="blank" class="blog">##</a><!--占位符--><slot name="desc"></slot>
</template><hello-user name="Shenfq" blog="http://blog.shenfq.com"><p slot="desc">欢迎关注公众号:更了不起的前端</p>
</hello-user>

这里用的用法与 Vue 的 slot 用法一致,不做过多的介绍。

总结

到这里 Web Components 的基本用法就介绍得差不多了,相比于其他的支持组件化方案的框架,使用 Web Components 有如下的优点:

  • 浏览器原生支持,不需要引入额外的第三方库;

  • 真正的内部私有化的 CSS,不会产生样式的冲突;

  • 无需经过编译操作,即可实现的组件化方案,且与外部 DOM 隔离;

Web Components 的主要缺点就是标准可能还不太稳定,例如文章中没有提到的模板的模块化方案,就已经被废除,现在还没有正式的方案引入模板文件。而且原生的 API 虽然能用,但是就是不好用,要不然也不会出现 jQuery 这样的库来操作 DOM。好在现在也有很多基于 Web Components 实现的框架,后面还会开篇文章专门讲一讲使用 Web Components 的框架 lit-htmllit-element

好啦,今天的文章就到这里了,希望大家能有所收获。


最近组建了一个江西人的前端交流群,如果你也是江西人可以加我微信ruochuan12 拉你进群。


常驻推荐阅读

若川知乎高赞:有哪些必看的 JS库?
我在阿里招前端,我该怎么帮你?(现在还可以加模拟面试群)
如何拿下阿里巴巴 P6 的前端 Offer
如何准备阿里P6/P7前端面试--项目经历准备篇
大厂面试官常问的亮点,该如何做出?
如何从初级到专家(P4-P7)打破成长瓶颈和有效突破
若川知乎问答:2年前端经验,做的项目没什么技术含量,怎么办?

常驻末尾

你好,我是若川,江西人~(点击蓝字了解我)历时一年只写了一个学习源码整体架构系列 有哪些必看的JS库:jQuery、underscore、lodash、sentry、vuex、axios、koa、redux

  1. 关注若川视野,回复"pdf" 领取优质前端书籍pdf,回复"1",可加群长期交流学习

  2. 我的博客地址:https://lxchuan12.gitee.io 欢迎收藏

  3. 觉得文章不错,可以 分享、点赞、在看 呀^_^另外欢迎留言交流~

小提醒:若川视野公众号面试、源码等文章合集在菜单栏中间【源码精选】按钮,欢迎点击阅读,也可以星标我的公众号,便于查找

Web Components 上手指南相关推荐

  1. Vue3官网-高级指南(十五)Vue 与 Web Components

    Vue3官网-高级指南(十五)Vue 与 Web Components 文章目录 Vue3官网-高级指南(十五)Vue 与 Web Components 1. Vue 与 Web Components ...

  2. 从 0 到 1 上手 Web Components 业务组件库开发

    组件化是前端发展的一个重要方向,它一方面提高开发效率,另一方面降低维护成本.主流的 Vue.js.React 及其延伸的 Ant Design.uniapp.Taro 等都是组件框架. ​ Web C ...

  3. 【总结】- 从 0 到 1 上手 Web Components 业务组件库开发

    组件化是前端发展的一个重要方向,它一方面提高开发效率,另一方面降低维护成本.主流的 Vue.js.React 及其延伸的 Ant Design.uniapp.Taro 等都是组件框架.Web Comp ...

  4. 【总结】1181- 从 0 到 1 上手 Web Components 业务组件库开发

    组件化是前端发展的一个重要方向,它一方面提高开发效率,另一方面降低维护成本.主流的 Vue.js.React 及其延伸的 Ant Design.uniapp.Taro 等都是组件框架.Web Comp ...

  5. python web-python web入坑指南

    原标题:python web入坑指南 Invest regularly in your knowledge portfolio. Make learning a habit. 自学python web ...

  6. [转载]Programming MS Office 2000 Web Components第二章第三节

    第二章第三节 编程模型要点 现在总结一下我们对电子表格组件的介绍,我将讲述(电子表格)控件编程模型的各个要点,以便您了解如何运用这个控件,以及当您需要编写脚本来实现不同的功能时该如何去做.本节不是编程 ...

  7. web前端入门指南:来看看这位大佬的学习之路吧!

    开始他的表演 第一章:为什么要学习前端开发?web前端开发是什么? 从事前端开发工作差不多3年了,自己也从一个什么都不懂的小白积累了一定的理论和实践经验.编写<Web前端入门指南>系列文章 ...

  8. NexentaStor iSCSI/ NAS 存储服务器软件图解教程(3)——NMV快速上手指南Part2

    NexentaStoriSCSI/NAS 存储服务器软件图解教程(3) Nexenta Management View (NMV)/*NexentaStor Web 管理界面*/快速上手指南Part ...

  9. Apipost 上手指南

    Apipost 上手指南 简介 Apipost是一个用于Web开发的接口调试工具,由国人开发. 官网:Apipost-API 文档.设计.调试.自动化测试一体化协作平台 官方文档:产品介绍 | Api ...

最新文章

  1. GPT-3会做小学应用题了!60亿击败1750亿,并非参数越大越好
  2. RocketMQ原理解析-producer 4.发送分布式事物消息
  3. SAP 请求号单个传输方法
  4. public private protected
  5. fprintf,sprintf,sscanf,fscanf
  6. html表单复选框样式,美化表单——自定义checkbox和radio样式
  7. Halcon 学习总结——制作标定板(函数gen_caltab)
  8. 施密特:乔布斯影响力还没有完全释放
  9. PS设置图片为透明背景简单操作
  10. SQL注入:sqli-labs lesson-1 小白详解
  11. 组装台式计算机需要哪些配件,组装一台电脑需要哪些配件【详细列举】
  12. vite安装失败问题Emitted ‘error‘ event on ChildProcess instance at:-4058esbuild.exe
  13. ADO.NET Data Providers
  14. 星星城堡童装加盟雾非雾
  15. 局域网共享上网IP设置
  16. USRP 套件在windows下的快速引入
  17. 怀念吗?那年下课回宿舍的日子
  18. java基础之this关键字_繁星漫天_新浪博客
  19. vue中v-for图片src路径错误
  20. 基于粤嵌6818开发板上通过网络连接实现文件传输

热门文章

  1. UIWebView UITextView
  2. 属性“dataProvider”有多个初始值设定项。(注意:“dataProvider”是“mx.charts.BarChart”的默认属性)。...
  3. jquery插件编写
  4. w3ctech 2011 北京站(组图)
  5. 嵌入式操作系统 NuttX 5.0 发布
  6. php debug用什么意思,phpdebug_backtrace()函数是干什么的?
  7. 虚拟化安全防护系统部署在安全服务器上,虚拟化安全防护-安天 智者安天下
  8. 使用说明 思迅收银系统_便利店收银使用的收银系统应该取决于什么?
  9. 查看是否打印GC收集细节
  10. 电脑技巧:Win10系统中的这六种模式介绍