Web Components 出来很久了,最早一八年的时候听说过,后来因为用不到就没关注,现在作为知识储备总结下它的能力。
初听到名字会想到类似于,Card,Select,Tooltip 等常用组件,同样也会疑惑和普通自己封装一个有什么区别?通过文档可以得知,它实现了样式隔离(事件还是会冒泡的),还有生命周期事件等回调。在 Web Components 中有三个概念,自定义元素,Shadow DOM,
自定义元素
我可以继承本身存在的元素类型去自定义,比如现在需要一个NumberInput
,用户输入非数字时,边框会变成红色,如果输入的是数字,那么就移除边框颜色,并且同时我会在验证的时候给节点添加一个 attribute
。
class NumberInput extends HTMLInputElement {
constructor() {
super();
this.style.outline = 'none';
}
connectedCallback() {
this.addEventListener('input', e => {
if (isNaN(e.target.value)) {
this.style.borderColor = 'red';
this.setAttribute('validate', 'false');
} else {
this.style.borderColor = '';
this.setAttribute('validate', 'true');
}
});
}
}
customElements.define('number-input', NumberInput, { extends: 'input' });
<input is="number-input" />
Shadow
这里是整个技术的核心,如果不用 Shdow 样式也不会隔离开,比如上面的 demo 还是会继承外部的样式。
class WordCountInput2 extends HTMLElement {
constructor() {
super();
this.dataId = Math.random();
const shadow = this.attachShadow({mode: 'open'});
const style = document.createElement('style');
style.textContent = `
span {
color: #999;
}
`;
const input = document.createElement('input');
const span = document.createElement('span');
span.innerText = '(0)';
input.addEventListener('input', e => {
span.innerText = `(${e.target.value.length})`;
});
shadow.appendChild(style);
shadow.appendChild(input);
shadow.appendChild(span);
}
}
customElements.define('word-count-input2', WordCountInput2);
<word-count-input2></word-count-input2>
Template
用过 Vue 的可能比较熟悉,这里不只有 <template>
还有<slot>
。
<template id='wordCountInput'>
<input />
<span>(0)</span>
</template>
<word-count-input></word-count-input>
class WordCountInput extends HTMLElement {
constructor() {
super();
const templateElem = document.getElementById('wordCountInput');
const content = templateElem.content.cloneNode(true);
const span = content.querySelector('span');
content.querySelector('input').addEventListener('input', e => {
span.innerText = `(${e.target.value.length})`;
});
this.appendChild(content);
}
}
Slot
class UserButton extends HTMLElement {
constructor() {
super();
const templateElem = document.getElementById('user-button');
const content = templateElem.content.cloneNode(true);
const shadow = this.attachShadow({mode: 'open'});
shadow.appendChild(content);
}
}
customElements.define('user-button', UserButton);
<user-button></user-button>
<user-button>
<span slot="text">Confirm</span>
</user-button>
Demo
最后,演示效果(代码地址)