Web Components 上手指南

现在的前端开发基本离不开 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. 觉得文章不错,可以 分享、点赞、在看 呀^_^另外欢迎留言交流~

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

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

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

相关文章

如何远程连接Windows和linux服务器

linux的方法在下面 Windows服务器远程连接 登录控制台查看服务器系统是什么系统例如阿里云的ECS服务器 Windows系统可以使用微软自带的远程工具进行连接&#xff0c;可以连接的系统有Windows server 和Windows 7-10 等等系列&#xff1b;Windows系统&#xff0c;例如Windows10系…

手把手教你接入前端热门抓包神器 - whistle

大家好&#xff0c;我是若川&#xff0c;今天推荐腾讯前端团队的这篇好文。whistle 是一个基于 Node.js 的跨平台网络调试工具。最近随着 Nohost 的开源&#xff0c;有不少同学问了 whistle 相关的问题&#xff0c;本篇文章将结合几个常见的业务场景介绍如何在本地前端项目开发…

React 与 Vue 框架的设计思路大 PK

大家好&#xff0c;我是若川。今天分享一篇框架设计思路的好文。关于我 大家好我是花果山的大圣&#xff0c;今天很荣幸&#xff0c;有机会跟大家分享一下很多年轻人感兴趣的话题《 Vue 和 React 设计思想 PK》,个人水平有限&#xff0c;如果有理解不到位的请倾盆&#xff0c;大…

php foreach id是否存在数组_请纠正这 5 个 PHP 编码小陋习

在做过大量的代码审查后&#xff0c;我经常看到一些重复的错误&#xff0c;以下是纠正这些错误的方法。在循环之前测试数组是否为空$items [];// ...if (count($items) > 0) {foreach ($items as $item) {// process on $item ...}}foreach以及数组函数 (array_*) 可以处理…

1161转进制(C语言)

一&#xff1a;题目 二&#xff1a;思路分析 1.首先该题目让我们使用递归求十进制转其他进制 2.其次&#xff0c;我们要知道十进制转换为其他进制怎么转换&#xff0c;以例题所给的数据为例 由此图可以看出&#xff0c;十进制转换为其他进制&#xff0c;是辗转相除法&#xf…

应对无协议脱欧 葡萄牙机场将为英籍旅客设快速通道

中新网1月18日电 据台湾《联合报》援引英媒报道&#xff0c;英国首相特蕾莎•梅的脱欧协议遭下院否决后&#xff0c;英国无协议脱欧的可能性变大。葡萄牙总理科斯塔17日表示&#xff0c;里斯本当局正对机场开设特殊通道进行规划&#xff0c;使英国旅客无论英国最后如何脱欧&…

6轮字节前端校招面试经验分享

大家好&#xff0c;我是若川。最近金三银四&#xff0c;今天分享一篇字节前端校招面试经验的轻松好文&#xff0c;相信看完会有所收获。也欢迎点击下方卡片关注或者星标我的公众号若川视野因为我错过了2020年的秋招&#xff08;ps: 那时候连数据结构与算法都还没学完&#xff0…

斥资近1亿港元,小米二次回购

1月21日消息&#xff0c;小米集团发布公告称&#xff0c;公司于1月18日回购了984.96万股B类普通股股票&#xff0c;占已发行股份0.041%&#xff0c;平均价为每股B类股10.1527港元&#xff0c;总计斥资近1亿港元。 这也是继1月17日首次回购后&#xff0c;小米集团连续两日出手进…

ios macos_设计师可以从iOS 14和macOS Big Sur中学到什么?

ios macos重点 (Top highlight)With the introduction of iOS 14 and macOS Big Sur, we are the witness of the next big thing in UI Design. Changes are not so revolutionary like in iOS 7 years before, but they undoubtedly present the trend UI Designers will fol…

网页设计简约_简约网页设计的主要功能

网页设计简约重点 (Top highlight)Minimalism is synonymous with simplicity. Not quite. As the name suggests, minimalism is definitely not about opulent design. But the assumption that minimalism is design-less and plain is also wrong. Minimalism is simple ye…

Expo 2010 Japan Pavilion

^_^转载于:https://www.cnblogs.com/mmmhhhlll/archive/2010/04/16/1713680.html

深度对比学习Vue和React两大框架

作为国内应用最广的两个框架&#xff0c;Vue 和 React 是前端必须掌握的内容&#xff0c;也是面试的重点。但大多数读者都只擅长其中一个框架&#xff0c;当面试涉及到另一个框架的内容时&#xff0c;就答不好了。比如虚拟dom&#xff0c;两个框架中都有应用&#xff0c;面试官…

java rwd_面向任务的设计-不仅限于Mobile First和RWD

java rwdWe already know that majority of solutions should start with a design for smartphones, we know that all websites should be responsive. Now, it’s time to think about holistic solutions with specific tasks adapted to all kind of devices.我们已经知道…

HOJ 1015 Nearly prime numbers

代码 //Nearly prime number is an integer positive number for which it is possible //to find such primes P1 and P2 that given number is equal to P1*P2.#include <stdio.h>#include <stdlib.h>#include <math.h>//decide n whither is a nearly pri…

「前端工程化」该怎么理解?

大家好&#xff0c;我是若川。今天分享一篇「前端工程化」的好文。非广告&#xff0c;请放心阅读。可点击下方卡片关注我&#xff0c;或者查看系列文章。今天发文比较晚&#xff0c;以往都是定时早上7:30发文&#xff0c;也不知道是不是有点早。一.什么是前端工程&#xff1f;一…

figma下载_Figma和ProtoPie中的原型制作,比较

figma下载第1部分 (Part 1) Prototyping has never had such a high profile with a whole host of tools that now give you varying ability to realize your designs beyond their static UI and into a working usable thing. It’s fair to say that prototyping within t…

「前端组件化」该怎么理解?

大家好&#xff0c;我是若川。今天分享一篇关于「前端组件化」的好文。欢迎点击下方卡片关注我。以下是正文~这里我们一起来学习前端组件化的知识&#xff0c;而组件化在前端架构里面是最重要的一个部分。讲到前端架构&#xff0c;其实前端架构中最热门的就有两个话题&#xff…

大屏设计的视觉统一_视觉设计中的统一

大屏设计的视觉统一视觉设计的统一性是什么&#xff1f; (What is unity in visual design?) The concept of unity in visual design means a group of elements working together to create a greater whole. It means, as the clich goes: A whole that is greater than th…

跟着官方文档能学懂React Hooks就怪了

大家好&#xff0c;我是若川。今天分享一篇关于「React Hooks」的好文。欢迎点击下方卡片关注我。以下是正文~回想下你入门Hooks的过程&#xff0c;是不是经历过&#xff1a;类比ClassComponent的生命周期&#xff0c;学习Hooks的执行时机慢慢熟练以后&#xff0c;发现Hooks的执…

origin图上显示数据标签_Origin(Pro):寒假都结束了,这个图还是不会画?【数据绘图】...

寒假前给大家分享了一个图&#xff0c;大家要的教程来了。【数据绘图】好图分享&#xff1a;寒假&#xff1f;不存在的&#xff01;​mp.weixin.qq.com绘图思路&#xff1a;左侧起止时间&#xff1a;散点图&#xff0c;交换XY坐标轴&#xff1b;中间的连线为Drop Lines&#xf…