在當下,前端三巨頭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傳入,因此都是字符串 二、組件之間的通訊傳遞須要經過自定義事件