未来组件化开发趋势WebComponent
优点:原生组件,不需要框架,性能好代码少。
缺点:兼容性问题
组件化好处: 高内聚、可重用、可组合
核心三项技术
- Custom elements:一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们
- Shadow DOM:一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
- HTML templates:
<template>
和<slot>
元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
一.实现自定义Button组件
组件的名字必须以中划线分割,避免与native标签冲突
- HTML templates:
<zf-button type="primary">珠峰架构</zf-button>
<template id="btn">
<button class="zf-btn">
<slot>按钮</slot>
</button>
</template>
1
2
3
4
5
6
7
2
3
4
5
6
7
template
中的内容是我们定义的button
组件的样子。slot
可以获取自定义组件中的内容,插入到模板对应的位置
- shadowDOM
shadow DOM 可以实现真正的隔离机制
class zfButton extends HTMLElement{
constructor(){
super();
// 创建影子
let shadow = this.attachShadow({mode:'open'});
let btn = document.getElementById('btn');
// 拷贝模板
let cloneTemplate = btn.content.cloneNode(true);
const style = document.createElement('style');
const types = {
'primary':{
backgroundColor:'#409eff',
color:'#fff'
},
'default':{
backgroundColor:'#c8c9cc',
color:'#fff'
}
}
const btnType = this.getAttribute('type') || 'default';
style.innerHTML = `
.zf-btn {
outline:none;
border:none;
border-radius:4px;
display:inline-block;
cursor:pointer;
padding:6px 20px;
background:var(--background-color,${types[btnType].backgroundColor});
color:var(--text-color,${types[btnType].color});
}
`
shadow.appendChild(style);
shadow.appendChild(cloneTemplate);
}
}
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
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
- customElement
window.customElements.define('zf-button',zfButton)
1
定义自定义组件
二.Collapse 折叠面板组件
- 定义组件使用方式
<h1>Collapse 组件</h1>
<zf-collapse>
<zf-collapse-item title="Node" name="1">
<div>Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行时。</div>
</zf-collapse-item>
<zf-collapse-item title="React" name="2">
<div>用于构建用户界面的 JavaScript 库</div>
</zf-collapse-item>
<zf-collapse-item title="Vue" name="3">
Vue.js 是一套构建用户界面的渐进式框架。
</zf-collapse-item>
</zf-collapse>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 定义组件模板
<template id="collapse_tpl">
<div class="zf-collapse">
<slot></slot>
</div>
</template>
<template id="collapse_item_tpl">
<div class="zf-collapse-item">
<div class="title"></div>
<div class="content">
<slot></slot>
</div>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 定制
zf-collapse
组件
class ZfCollapse extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const tmpl = document.getElementById('collapse_tpl');
let cloneTemplate = tmpl.content.cloneNode(true);
const style = document.createElement('style');
style.innerHTML = `
:host{
display:flex;
width:400px;
border:2px solid #f1f1f1;
padding:20px 15px;
border-radius:5px;
}
.zf-collapse{
width:100%;
}
.title{
font-size:30px;
}
`
shadow.appendChild(style);
shadow.appendChild(cloneTemplate);
// 获取插槽
const slot = shadow.querySelector('slot');
// 拿到插槽中所有内容
slot.addEventListener('slotchange', (e) => {
this.slotList = e.target.assignedElements(); // 获取插槽中的内容
this.render();
})
}
static get observedAttributes() {
return ['active']
}
attributeChangedCallback(name, oldVal, newVal) { // 获取属性列表
if (name === 'active') {
this.activeList = newVal;
this.render();
}
}
render() {
if (this.slotList && this.activeList) {
[...this.slotList].forEach(child => {
child.setAttribute('active', this.activeList)
});
}
}
}
export default ZfCollapse
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
42
43
44
45
46
47
48
49
50
51
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
42
43
44
45
46
47
48
49
50
51
- 定制
zf-collapse-item
组件
class ZfCollapseItem extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const tmpl = document.getElementById('collapse_item_tpl');
let cloneTemplate = tmpl.content.cloneNode(true);
const style = document.createElement('style');
style.innerHTML = `
:host{
width:100%;
display:block
}
.title{
background:#f1f1f1;
line-height:35px;
cursor:pointer;
width:100%;
user-select:none
}
.title, .content{
padding-left:5px
}
.content{
line-height:30px;
}
`;
shadow.appendChild(style);
shadow.appendChild(cloneTemplate);
this.header = this.shadowRoot.querySelector('.title');
this.show = true;
this.header.addEventListener('click', () => {
let nameVal = this.getAttribute('name');
this.show = !this.show;
document.querySelector('zf-collapse').dispatchEvent(new CustomEvent('change', {
detail: {
name: nameVal,
show: this.show
}
}))
});
}
static get observedAttributes() {
return ['active', 'name', 'title']
}
attributeChangedCallback(name, oldVal, newVal) {
switch (name) {
case 'active':
this.active = JSON.parse(newVal)
break;
case 'name':
this.name = newVal;
break;
case 'title':
this.header.innerHTML = newVal
break;
}
if (this.active && this.name) {
let isShow = this.active.includes(this.name);
this.shadowRoot.querySelector('.content').style.display = isShow ? 'block' : 'none';
this.show = isShow
}
}
}
export default ZfCollapseItem
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
- 引用组件
import ZfCollapse from './zf-collapse';
import ZfCollapseItem from './zf-collapse-item';
let currentActive = ['1', '2', '3'];
// 定制组件
customElements.define('zf-collapse', ZfCollapse);
customElements.define('zf-collapse-item', ZfCollapseItem);
// 设置数据
document.querySelector('zf-collapse').setAttribute('active', JSON.stringify(currentActive))
document.querySelector('zf-collapse').addEventListener('change', (e) => {
let { name, show } = e.detail;
if (show) {
currentActive.push(name);
} else {
let index = currentActive.indexOf(name);
currentActive.splice(index, 1);
}
document.querySelector('zf-collapse').setAttribute('active', JSON.stringify(currentActive))
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
三.WebComponent生命周期
connectedCallback
:当custom element首次被插入文档DOM时,被调用disconnectedCallback
:当 custom element从文档DOM中删除时,被调用adoptedCallback
:当 custom element被移动到新的文档时,被调用 (移动到iframe中)attributeChangedCallback
:当 custom element增加、删除、修改自身属性时,被调用