随笔

Web Components

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

最后,演示效果(代码地址)

截屏2021-12-08 10.25.19.png

本文链接:https://note.lilonghe.net/post/web-components.html

-- EOF --