看了Quark Design后,我去深入了解了Web Components

开发 前端
现今前端生态中框架层出不穷,在生产中为了提升效率和标准化组件,就会针对框架开发组件库。但是各种框架之间是不兼容的,对此需要对应开发适应框架的组件库,这样也造成维护成本攀升。就如同物理界有电磁相互作用、强相互作用以及弱相互作用,需要单独的物理理论来解释这三种作用造成的物理现象,这样让众多物理学家在朝着大统一理论前进,而Web Components可以看作是现今UI组件库"大统一理论"的一种解决方案。

​1.写在前面

最近哈啰单车前端团队开源的Quark Design组件库,号称是下一代前端组件库,可以同时在任意框架或无框架中使用。

那么,什么是Web Components?

2.什么是Web Components?

现今前端生态中框架层出不穷,在生产中为了提升效率和标准化组件,就会针对框架开发组件库。但是各种框架之间是不兼容的,对此需要对应开发适应框架的组件库,这样也造成维护成本攀升。就如同物理界有电磁相互作用、强相互作用以及弱相互作用,需要单独的物理理论来解释这三种作用造成的物理现象,这样让众多物理学家在朝着大统一理论前进,而Web Components可以看作是现今UI组件库"大统一理论"的一种解决方案。

图片

Web Components与React、Vue​等框架中的组件类似,其实在Vue中也采用了很多基于Web Components​的设计。这是一个可复用的UI构建模块,封装了所有渲染所需要的HTML、CSS​以及基于Javascript​的逻辑。最大的区别是,它不依赖于特定的JavaScript框架,而是利用浏览器原生提供的技术,这样你的Web组件就与框架无关了。

3.Web Components的主要内容

Web Components的主要内容如下:

  • Custom Elements​:原生提供的API,用于可自定义Custom Elements和行为。
  • Shadow DOM:原生提供的API,用于封装与主文档DOM隔离的私有DOM,不受外部DOM的样式和行为的影响。
  • Templates:
  • Attributes:
  • Slots:
  • Life cycle methods:

接下来,跟随我的脚步在例子中逐个实现这些概念,逐个击破理论。

4.Custom Elements

Custom Elements是Web Components​中的一个重要特性,可以提供给开发者将html的功能封装为自定义标签,方便进行复用。也就是说,Custom Elements​本质上是用户用于实现在html​中有效使用的,类似<div>、<button>​等自定义标签。最大区别在于,自定义标签有着自己的模板、行为和标签名称,通过Javascript的API实现的。

Custom Elements​总是使用连字符-​进行自定义标签的标识,即<one-button/>​,各大浏览器公司达成公司不在创建任何新的标签元素,来防止元素冲突。熟悉vue框架的开发者应该知道,在vue中的自定义组件命令有<OneButton/>或<one-button/>​这种结构,官方推荐的是<one-button/>这种使用方式。

通过customElements.define来简单注册自定义组件,下面就来简单实践下:

// one-button.js
// 1、创建自定义组件的类
class OneButton extends HTMLElement{
constructor(){
super()
// 2、创建组件内容
this.innerHTML = `<button>hello onechuan</button>`
}
}

// 3、进行自定义组件的注册
customElements.define("one-button", OneButton)

// index.html
// 4、html中进行使用
<one-button></one-button>
<script src="./one-button.js"></script>

运行展示:

图片

5.Shadow DOM

Shadow DOM​与原生DOM的区别就是用户自定义封装的DOM,本质上还是DOM元素。不同的是,Shadow DOM​可以将自定义的DOM片段与其他DOM进行隔离,可以实现样式不受外部DOM的影响,类似于使用iframe​内嵌了一个html。也正是Shadow DOM的存在,能够进行DOM隔离、实现独立的组件王国,使得自定义组件跨域框架的约束,不再耦合,确保在任何无框架和任何框架中正常使用。

如图所示:

图片

图示来自MDN文档

一些 Shadow DOM 特有的术语:

  • Shadow host:一个常规 DOM 节点,Shadow DOM 会被附加到这个节点上。
  • Shadow tree:Shadow DOM 内部的 DOM 树。
  • Shadow boundary:Shadow DOM 结束的地方,也是常规 DOM 开始的地方。
  • Shadow root: Shadow tree 的根节点。

Shadow DOM ​并不是新生事物,在原生<video>​标签中其实就有内置的Shadow DOM ​,包含一系列的按钮和其他控制器等。而Web Components​只不过是允许用户根据需要使用Shadow DOM来实现自定义标签。

那么,就接着上面的例子一起来实现下:

// one-button.js
// 1、创建自定义组件的类
class OneButton extends HTMLElement{
constructor(){
super()
// 2、创建shadow dom
this.attachShadow({mode: 'open'})
this.shadowRoot.innerHTML = `<button>hello onechuan</button>`
}
}

// 3、进行自定义组件的注册
customElements.define("one-button", OneButton)

运行得到:

图片

在上面运行结果中,实现的展示效果是一样,但是渲染的dom节点却有所不同,在使用shadow dom​实现的标签中会有#shadow-root字样标识。

图片

接下来,我们给他们添加上样式,进行对比:

<button>hello yichuan</button>
<one-button></one-button>
<style>
button{
font-size: 15px;
padding: 12px;
background-color: #e02727;
color: #fff;
line-height: 15px;
cursor: pointer;
border: none;
}
</style>
<script>
// 1、创建自定义组件的类
class OneButton extends HTMLElement{
constructor(){
super()
// 2、创建shadow dom
this.attachShadow({mode: 'open'})
this.shadowRoot.innerHTML = `<button>hello onechuan</button>`
}
}

// 3、进行自定义组件的注册
customElements.define("one-button", OneButton)
</script>

运行结果:

图片

在上面代码片段中,在全局对button​标签进行设置样式,运行结果表明并未影响到shadow dom中的样式。

6.Template

见到template​模板,使用过vue框架的开发者就再熟悉不过了,template​标签本身不会在html​中进行立即渲染,而是用于对待使用的代码进行标记,在需要被使用的时候才会进行渲染。也正是因为template​的特性,使得我们可以将一些可重复使用的代码用template​进行组织,通过javascript获取它的引用,再添加到DOM中。

For Example:

<one-button></one-button>
<template>
<button>hello yichuan</button>
</template>
<script>
const template = document.createElement("template")
template.innerHTML = `<button>hello onechuan</button>`
// 1、实现自定义组件的类
class OneButton extends HTMLElement{
// 2、定义自定义组件的内容
constructor(){
super()
// 2-1、创建影子DOM
this.attachShadow({mode: "open"})
// 2-2、设置影子DOM的内容

this.shadowRoot.appendChild(template.content.cloneNode(true))
}
}
// 3、注册自定义组件
customElements.define("one-button", OneButton)
</script>

图片

在上面代码片段中,可以看到在需要使用的时候将模板内容追加到shadow dom中,即模板内容不会立即被渲染,而是在被调用后才会在页面进行渲染。

注意:

为什么这里使用Node.cloneNode() 方法添加了模板的拷贝到阴影的根结点上?

这是因为在添加模板内容到shadow dom​时,后续可以添加样式信息到模板的style标签上,也会将其封装到自定义标签中,倘若直接将模板添加到原生DOM元素中不起作用的。

7.Slots

插槽由其name属性标识,并且允许您在模板中定义占位符,当在标记中使用该元素时,该占位符可以填充所需的任何 HTML 标记片段。使用过vue框架的开发者,对这部分内容再熟悉不过了。

<one-button>
<button>hello yichuan</button>
</one-button>
<script>
const template = document.createElement("template")
template.innerHTML = `
<div>
<slot><button>hello onechuan</button></slot>
</div>
`
class OneButton extends HTMLElement{
constructor(){
super()
this.attachShadow({mode:"open"})
this.shadowRoot.appendChild(template.content.cloneNode(true))
}
}
customElements.define("one-button",OneButton)
</script>

运行结果:

图片

在上面片段中,在使用自定义标签的时候,如果<one-button>​中没有插入任何内容,展示的将是hello onechuan​,倘若在在使用时插入了自定义内容,则展示自定义内容hello yichuan。

当然,当自定义组件中有多个地方使用插槽,可以通过给每个插槽命名进行标识,即:命名插槽。

类似于:vue中的mount。

<one-button>
<button>hello yichuan</button>
<span slot="desc">
slot 这个我熟呀
</span>
</one-button>
<script>
const template = document.createElement("template")
template.innerHTML = `
<div>
<slot>
<button>hello onechuan</button>
</slot>
<slot name="desc"></slot>
</div>
`
class OneButton extends HTMLElement{
constructor(){
super()
this.attachShadow({mode:"open"})
this.shadowRoot.appendChild(template.content.cloneNode(true))
}
}
customElements.define("one-button",OneButton)
</script>

运行结果:

图片

​connectedCallback

connectedCallback​方法会在自定义组件插入DOM的首次渲染时调用一次。此时 shadow dom​已经挂载到DOM树上,对此可以通过connectedCallback​方法来访问shadow dom上的属性、子元素以及监听事件。倘若组件被移除或被移动,将会再次被调用。

<one-button></one-button>
<script>
customElements.define("one-button",class extends HTMLElement{
constructor(){
super()
this.attachShadow({mode:"open"})
this.shadowRoot.innerHTML = `
<button>登录</button>
`
}
connectedCallback(){
console.log("初始化");
this.addEventListener("click",()=>{
console.log("this id a click handler");
})
}
})
</script>

运行结果:

图片

在上面代码片段中,看到在进行渲染初始化后,会进行执行一次connectedCallback的内容。

​attributeChangedCallback

监听custom element​自身属性的增删改,当发生状态改变时调用此方法,在使用前必须在一个observedAttributes() ​的静态方法中定义要观察的属性。该方法返回一个属性名称的数组。一旦observedAttributes​返回了属性值数组,则attributeChangedCallback方法会在每次该属性变化时调用。

这个方法有三个参数:属性名称被改变,该属性的旧值,以及更新的值。当this.setAttribute()触发时,属性会被更新。

disconnectedCallback

当 custom element​ 从文档 DOM​ 中删除时,disconnectedCallback被调用。

<script>
customElements.define(
"one-button",
class extends HTMLElement {
//...
disconnectedCallback() {
this.removeEventListener("click", ()=>{
console.log("clicked handled");
});
}
}
);

</script>

8.写在最后

在我们上述实现的自定义组件,可以在整个项目中重复使用。简而言之,在构建Web Components时,步骤如下:

  • 首先,必须使用customElements.define()注册自定义元素;
  • 构造函数会调用,将节点添加到shadow dom中
  • 然后,进行一次初始化,执行connectedCallback方法;
  • 当元素的属性被更新时,它的attributeChangedCallback()被触发;
  • 当一个事件从元素中被移除时,disconnectedCallback()方法被调用以清理事件监听器。
  • shadow dom - 是DOM的一个封装版本,用于防止CSS泄露。
  • Slots ​- 用于添加和访问自定义元素组件的子元素,也有一种特殊的方式来访问使用命名为Slot的子元素。

本文参考和整理了许多文档资料,学而知不足,继续前行。

9.参考文章

《Web Components Basic to Advanced》

《MDN文档》

《重磅!哈啰 Quark Design 正式开源,下一代跨技术栈前端组件库》

责任编辑:武晓燕 来源: 前端一码平川
相关推荐

2010-11-19 16:22:14

Oracle事务

2010-06-23 20:31:54

2010-07-13 09:36:25

2020-09-21 09:53:04

FlexCSS开发

2009-08-25 16:27:10

Mscomm控件

2022-08-26 13:48:40

EPUBLinux

2020-07-20 06:35:55

BashLinux

2022-06-03 10:09:32

威胁检测软件

2010-11-15 11:40:44

Oracle表空间

2011-07-18 15:08:34

2010-09-27 09:31:42

JVM内存结构

2010-11-08 13:54:49

Sqlserver运行

2018-06-22 13:05:02

前端JavaScript引擎

2021-04-28 10:13:58

zookeeperZNode核心原理

2021-01-19 12:00:39

前端监控代码

2013-04-16 10:20:21

云存储服务云存储SLA服务水平协议

2019-11-29 16:21:22

Spring框架集成

2009-09-24 16:22:50

Hibernate自动

2013-10-09 11:07:31

日志文件系统
点赞
收藏

51CTO技术栈公众号