WebComponents
是一项在网页里面直接进行组件化开发的标准,帮助你在不使用任何框架的前提下,进行自定义组件开发。
开发流程
要开发一个自定义组件,非常简单,尤其在你使用过vue
等框架,对组件化概念有一定认识之后:
- 创建一个类或函数来定义
web
组件 - 使用
CustomElementRegistry.define()
这个api来注册第一步定义的组件 - 组件内部可使用
ShadowDom
技术来实现内部细节 - 通过
template
和slot
元素,在组件内部与外部之间构建灵活的内容替换方式 - 在页面其它位置,像普通
html
元素一样使用前面开发好的自定义组件。
创建自定义组件的类
有两种方式创建自定义组件的类:
- 创建一个独立的自定义组件类
示例:1
2
3
4
5
6
7
8class PopUpInfo extends HTMLElement {
constructor() {
// 必须首先调用 super方法
super();
// 其它构造写在这里
}
}
这个方式中,通过继承HTMLElement
得到一个PopUpInfo
类,这个类代表了一个新的自定义组件。 HTMLElement
接口,是所有html
元素都间接或直接继承的接口。 所以直接继承这个类的方式,得到的自定义组件,是一个完全独立的新组件,可以把它当成一个新的html
元素来看待。
- 基于已有的标准
html
元素继承
示例:1
2
3
4
5
6
7
8class WordCount extends HTMLParagraphElement {
constructor() {
// 必须首先调用 super方法
super();
// 其它构造写在这里
}
}
这个方式中,通过继承HTMLParagraphElement
得到一个WordCount
类,HTMLParagraphElement
从字面含义可知,它代表的是p
元素。 WordCount
类继承了p
元素之后,将具备p
元素的自身的特性。 这样得到的一个自定义组件,在注册和使用时,与前面一种方式得到的自定义组件,是有区别的。
前面总结的两种方式都有共同点:必须有明确的extend
目标,必须有constructor
函数,必须调用super()
函数。
注册和使用自定义组件
使用CustomElementRegistry.define()
这个api来注册自定义组件,也有两种方式。通过window.customElements
可以得到一个CustomElementRegistry
的实例。
- 注册独立使用的自定义组件
1 | customElements.define('popup-info', class PopUpInfo extends HTMLElement { |
以上把PopUpInfo
注册为了一个自定义组件,并且可以在html
中通过popup-info
标签来直接使用。如:1
<popup-info>
在document.createElement
中,也可以直接使用:1
document.createElement('popup-info')
- 注册继承了标准元素的自定义组件
1 | customElements.define('expanding-list', class ExpandingList extends HTMLUListElement { |
这类组件不能直接用注册的标签名称使用,而是要基于它扩展的标准元素、结合is
这个html
属性来使用。如:1
2<ul is="expanding-list">
</ul>
在document.createElement
中,也要做调整,使用is
option:1
document.createElement('ul', {is: 'expanding-list'})
两种组件注册时的共同要求:标签名称必须是用短横线分割的多个单词,且必须符合DOMString
标准。
使用ShadowDom
在自定义组件的内部,通过ShadowDom
来构造内部的细节。 从编程的角度来说,组件要与外部进行职责分离,内部细节应该对外不可见;从html
元素的角度来说,元素最终都是通过dom
结构来展现的。所以ShadowDom
就出现了。首先它就是一份普通的dom
结构,但与我们常规看到的dom
不一样的是,它在它所依附的元素外部,是不可见的。而且,它不是一个新技术概念,浏览器在过去,就已经在使用ShadowDom
来封装内部标准元素。比如说video
元素,我们在页面中使用的时候,它都自带控制栏,这个控制栏就是ShadowDom
完成的。
ShadowDom
通过附加在某个常规的dom
节点上,间接地参与了整个页面的渲染,这个被附加的dom
节点称为ShadowHost
。ShadowDom
内部就跟普通dom
没有区别,它也有跟document
一样的跟节点,称为ShadowRoot
。我们把document
结构称为document tree
,ShadowDom
结构称为ShadowTree
。 这是它们的关系:
使用实践
通过Element.attachShadow()
,可以给任意元素附加一个ShadowDom
。1
2let shadow = elementRef.attachShadow({mode: 'open'});
let shadow = elementRef.attachShadow({mode: 'closed'});
这个方法调用后偶返回了ShadowDom
的ShadowRoot
引用。attachShow
使用时,有一个mode
的option,它可以配置为:closed
或open
。当它是open
的时候,回头这个附加了ShadowDom
的元素,可以通过下面的方式访问ShadowDom
:1
elementRef.shadowRoot
而closed
option则不可以。所以closed
option特别适合用在自定义组件里面。
当你拿到ShadowRoot
之后,就可以往ShadowDom
里面,添加普通的DOM
节点了:1
2
3
4
5
6
7
8
9
10
11
12
13class PopUpInfo extends HTMLElement {
constructor() {
super();
// 附件shadowdom
const shadow = this.attachShadow({mode: 'open'});
// 创建常规dom节点
const wrapper = document.createElement('span');
wrapper.setAttribute('class', 'wrapper');
shadow.appendChild(wrapper);
}
}
自定义元素可以通过html
的attribute
往组件内部传递外部的数据:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class PopUpInfo extends HTMLElement {
constructor() {
super();
// 附件shadowdom
const shadow = this.attachShadow({mode: 'open'});
// 创建常规dom节点
const wrapper = document.createElement('span');
wrapper.setAttribute('class', 'wrapper');
const info = document.createElement('span');
// 获取自定义元素在页面中注册的属性
const text = this.getAttribute('data-text');
info.textContent = text;
wrapper.appendChild(info);
shadow.appendChild(wrapper);
}
}
1 | <popup-info data-text="..."> |
还可以在ShadowDom
里面编写css
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42class PopUpInfo extends HTMLElement {
constructor() {
super();
// Create a shadow root
const shadow = this.attachShadow({mode: 'open'});
const style = document.createElement('style');
style.textContent = `
.wrapper {
position: relative;
}
.info {
font-size: 0.8rem;
width: 200px;
display: inline-block;
border: 1px solid black;
padding: 10px;
background: white;
border-radius: 10px;
opacity: 0;
transition: 0.6s all;
position: absolute;
bottom: 20px;
left: 10px;
z-index: 3;
}
img {
width: 1.2rem;
}
.icon:hover + .info, .icon:focus + .info {
opacity: 1;
}
`
shadow.appendChild(style);
}
}
总之,在ShadowDom
里面能做的事情,与在document
中是没啥区别的,它们就是两个完全隔离的dom
渲染区域。
使用template和slot
不同于上面直接在js中写内部的html
结构和样式,自定义组件还能利用template
和slot
这个元素来实现更加强大的html
结构构建功能。可参考:使用template和slot
使用生命周期函数
在自定义组件中还能使用生命周期函数,来开发更强的组件,可参考: 使用生命周期函数
实例
mdn
上有提供诸多实例,包含了web component
相关的各个要点,在真正想去实践web component
时,可以拿来研究参考:mdn实例