在当下,前端三巨头vue react ng都是提倡组件化开发的,在原生领域,web-components也逐渐成为标准。近段时间大热的omi就是基于web-components实现的javascript
本文github地址css
web-components主要由3部分组成html
从字面意思能够知道这是自定义元素的意思。区别于原生html元素,咱们能够本身定义它的行为。按照是否从原生html元素继承,可分下面两类前端
custom-elements 比较赞的一点是具备如下的生命周期vue
connectedCallback 链接到dom后触发 相似于react的componentDidMount,当自定义元素首次加载到dom会触发,若是咱们想获取传入的attributes来选择展现内容的话,须要将逻辑放在这个周期内而不是constructor中,constructor是取不到attributes的值,还须要注意的是,受html限制,经过html传入的attributes值都是字符串java
disconnectedCallback 当自定义元素从DOM树中脱离触发 对于绑定元素的事件监听,能够在这里进行解绑,防止内存泄漏react
adoptedCallback 当自定义元素移动到新的document触发git
attributeChangedCallback 自定义元素属性值改变时触发。这个须要配合static get observedAttributes(){return ['须要监听的属性']}
使用,表示哪些属性变化才会触发这个生命周期。对于动态attributes进行渲染,这个很是好用github
一个Autonomous custom elements web-components一般使用方法以下web
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则是相似
class CustomP extends HTMLParagraphElement {
...
}
customElements.define('custom-p', CustomP,{extend:'p'});
复制代码
shadom-dom操做和日常的dong操做差很少,对this.attachShadow({mode: 'open'});
。shadow-dom最大的好处就是实现了dom隔离。例如css只会对内部的shadow-dom有效,并不影响外部的元素。这应该是css最完美的解决方案了,目前不少组件化css解决方案css modules、各类css in js都不太优雅
// 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);
复制代码
相似于vue的概念,用来实现html复用和插槽效果
<template id="my-paragraph">
<style> p { color: white; background-color: #666; padding: 5px; } </style>
<p>My paragraph</p>
</template>
复制代码
// 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));
}
})
复制代码
web-components的使用很是方便,有几种方法 一、直接html中使用自定义标签
<custom-element></custom-element>
复制代码
二、经过js引入
const CustomElement = customElements.get('custom-element');
const customElement = new CustomElement();
// or
document.createElement('custom-elemen')
// append进dom
复制代码
实际开发结合polymer体验更佳
最后写了个web-compoennts todolist
代码以下
// 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);
复制代码
// 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);
复制代码
<!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>
复制代码
一些须要注意的地方: 一、经过html传递属性值,因为是经过attributes传入,因此都是字符串 二、组件之间的通讯传递须要经过自定义事件