初识web-components和todolist实现
flytam Lv4

在当下,前端三巨头vue react ng都是提倡组件化开发的,在原生领域,web-components也逐渐成为标准。近段时间大热的omi就是基于web-components实现的

web-components主要由3部分组成

  • custom-elements
  • shadow-dom
  • slot template

custom-elements

从字面意思可以知道这是自定义元素的意思。区别于原生html元素,我们可以自己定义它的行为。按照是否从原生html元素继承,可分下面两类

两类custom元素
  • Autonomous custom elements。完全自定义元素
  • Customized built-in elements .从常规html元素继承的
生命周期

custom-elements 比较赞的一点是具有以下的生命周期

  • connectedCallback 连接到dom后触发
    类似于react的componentDidMount,当自定义元素首次加载到dom会触发,如果我们想获取传入的attributes来选择展示内容的话,需要将逻辑放在这个周期内而不是constructor中,constructor是取不到attributes的值,还需要注意的是,受html限制,通过html传入的attributes值都是字符串

  • disconnectedCallback 当自定义元素从DOM树中脱离触发
    对于绑定元素的事件监听,可以在这里进行解绑,防止内存泄漏

  • adoptedCallback 当自定义元素移动到新的document触发

  • attributeChangedCallback 自定义元素属性值改变时触发。这个需要配合static get observedAttributes(){return ['需要监听的属性']}使用,表示哪些属性变化才会触发这个生命周期。对于动态attributes进行渲染,这个非常好用

一个Autonomous custom elements web-components通常使用方法如下

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
class App extends HTMLElement {
static get observedAttributes() {
return ['text'];
}

constructor() {
super();
// 在constructor中初始化
// 创建一个shadow元素,会css隔离的,一些原生html元素例如video等也是基于shadowdom实现的
const shadow = this.attachShadow({mode: 'open'});

const div = document.createElement('div');

// web-components内的样式,外部不影响
const style = document.createElement('style');

shadow.appendChild(style);
shadow.appendChild(div);
}

connectedCallback() {}

disconnectedCallback() {}

adoptedCallback() {}

attributeChangedCallback(name, oldValue, newValue) {}
}

customElements.define('my-app', App);

如果是扩展原生元素的web-components则是类似

1
2
3
4
class CustomP extends HTMLParagraphElement {
...
}
customElements.define('custom-p', CustomP,{extend:'p'});

shadom-dom

shadom-dom操作和平常的dong操作差不多,对this.attachShadow({mode: 'open'});。shadow-dom最大的好处就是实现了dom隔离。例如css只会对内部的shadow-dom有效,并不影响外部的元素。这应该是css最完美的解决方案了,目前很多组件化css解决方案css modules、各种css in js都不太优雅

1
2
3
4
5
6
7
8
9
// this是custom-element
const shadow = this.attachShadow({mode: 'open'});

const div = document.createElement('div');

const style = document.createElement('style');

shadow.appendChild(style);
shadow.appendChild(div);

template 和 slot

类似于vue的概念,用来实现html复用和插槽效果

template结合custom-elements用法
1
2
3
4
5
6
7
8
9
10
<template id="my-paragraph">
<style>
p {
color: white;
background-color: #666;
padding: 5px;
}
</style>
<p>My paragraph</p>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
// mdn例子
customElements.define('my-paragraph',
class extends HTMLElement {
constructor() {
super();
let template = document.getElementById('my-paragraph');
let templateContent = template.content;

const shadowRoot = this.attachShadow({mode: 'open'})
.appendChild(templateContent.cloneNode(true));
}
})
slot用法则和vue的基本一致

使用

web-components的使用非常方便,有几种方法
1、直接html中使用自定义标签

1
<custom-element></custom-element>

2、通过js引入

1
2
3
4
5
6
const CustomElement = customElements.get('custom-element');
const customElement = new CustomElement();
// or
document.createElement('custom-elemen')

// append进dom

实际开发结合polymer体验更佳

最后写了个web-compoennts todolist

demo

代码如下

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
// TodoList.js
class TodoList extends HTMLElement {
constructor() {
super();
this.shadowdom = this.attachShadow({ mode: "open" });
this.handleRemove = this.handleRemove.bind(this);
}

get data() {
const dataAttribute = this.getAttribute("data");
if (dataAttribute) {
return Array.isArray(dataAttribute)
? dataAttribute
: JSON.parse(dataAttribute);
} else {
return [];
}
}

set data(val) {
this.setAttribute("data", JSON.stringify(val));
this.render();
}

handleRemove(e) {
this.remove(e.detail.index);
}

connectedCallback() {
this.render();
this.shadowdom.addEventListener("sub", this.handleRemove);
}
disconnectedCallback() {
this.shadowdom.removeEventListener("sub", this.handleRemove);
}
//渲染内容
render() {
// 简便起见,每次渲染前先清空shadowdom的内容
let last = null;
while ((last = this.shadowdom.lastChild)) {
this.shadowdom.removeChild(last);
}
this.data.forEach((item, index) => {
const todoiterm = new (customElements.get("todo-iterm"))();
todoiterm.innerHTML = `<span slot='text'>${item}</span>`;
todoiterm.setAttribute("data-index", index);

this.shadowdom.appendChild(todoiterm);
});
}

addIterm(text) {
this.data = [...this.data, text];
}
remove(deleteIndex) {
this.data = this.data.filter((item, index) => index != deleteIndex);
}
}
customElements.define("todo-list", TodoList);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// TodoIterm.js
class TodoIterm extends HTMLElement {
constructor() {
super();
const template = document.getElementById("list-item");
const templateContent = template.content;
const shadowdom = this.attachShadow({ mode: "open" });

shadowdom.appendChild(templateContent.cloneNode(true));
shadowdom.getElementById("sub").onclick = e => {
const event = new CustomEvent("sub", {
bubbles: true,
detail: { index: this.dataset.index},
});
this.dispatchEvent(event)
};
}
}
customElements.define("todo-iterm", TodoIterm);
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>web-components</title>
<script src="./TodoList.js"></script>
<script src="./TodoIterm.js"></script>
</head>
<body>
<template id="list-item">
<style>
* {
color: red;
}
</style>
<li><slot name="text">nothing write</slot><button id="sub">-</button></li>
</template>
<!-- <todo-list></todo-list> -->
<div>
<input id='input'/>
<button id='add'>+</button>
</div>
<script>
// 加载web compoennts
const List = customElements.get('todo-list');
const todoList = new List()

document.body.appendChild(todoList)
document.getElementById('add').onclick = function(){
const value = document.getElementById('input').value
todoList.addIterm(value)
}
</script>
</body>
</html>

一些需要注意的地方:
1、通过html传递属性值,由于是通过attributes传入,所以都是字符串
2、组件之间的通信传递需要通过自定义事件

  • Post title:初识web-components和todolist实现
  • Post author:flytam
  • Create time:2019-01-10 16:15:18
  • Post link:https://blog.flytam.vip/初识web-components和todolist实现.html
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.