Web Component 快速入门

4,427 阅读5分钟

什么是 Web Component

Web Component 是一种 W3C标准 支持的 组件化方案,通过它,我们可以编写可复用的 组件,同时,我们也可以对自己的组件做更精细化的控制。正如 PWA 一样,他并非一项单一的技术,而是由三项技术组成

  1. Shadow DOM
  2. Custom elements
  3. HTML templates

下面,我们从一个简单的例子来入手。

从一个例子说起

我们准备编写一个 TextReverse 组件,TextReverse 只有一个很简单的功能,就是把传入的 字符串颠倒显示

例如: <text-reverse text='123'></text-reverse> 将会显示 321

第一步,我们需要 定义 这个自定义组件。

class TextReverse extends HTMLElement {
    constructor() {
        super();
        const shadowRoot = this.attachShadow({ mode: 'open' });
        const text = this.getAttribute('text') || '';
        const wrapper = document.createElement('span');
        wrapper.textContent = text.split('').reverse().join('');
        shadowRoot.appendChild(wrapper);
    }
}

定义组件的方式也十分简单,我们只需要 继承一下 HTMLElement,然后在 构造函数 中编写自己的 初始化逻辑 就可以了。

初始化过程中,我们首先 创建了一个 shadowRoot,这个相当于是我们整个组件的一个 根结点

紧接着,我们获取到自身的 text 属性,并且将其 倒置 放入新创建的 span 元素中。

最后,我们把带有 textspan 塞入 shadowRoot

定义完成之后,我们要告知一下系统,也就是 组件注册

customElements.define(
    'text-reverse',
    TextReverse
)

这里有一个小细节,就是我们注册的名字必须是带短横线的。

注册完成之后就可以正式使用啦。

<text-reverse text='12345'></text-reverse>

Shadow Dom

上面的例子中,我们用到了 shadow root他承载着我们组件所有的内容。而他也是 Web Component 核心技术。

我们都知道 Dom 其实就是一棵树,而我们的组件则是树上的一个节点。我们可以称组件节点为 shadow host

shadow host 中含有一颗与外界隔离的 dom 树,我们称之为 shadow tree。shadow tree 中的内容不会影响到外界。Shadow Root 则是这一课shadow tree 的根节点。

结构如图所示:

样式隔离

shadow dom 一大亮点就是样式隔离。我们可以给之前的例子加上样式。

class TextReverse extends HTMLElement {
    constructor() {
        super();
        // ...
        const style = document.createElement('style');
        style.textContent = `* {
            background: red;
        }`
        shadowRoot.appendChild(style);
        // ...
    }
}

我们给所有元素添加一个红的背景色。但是,结果只有组件内的元素背景色受到了影响。这种样式隔离的特性很好地避免了不同组件之间的样式干扰。

Template

在上面的例子中,我们采用代码的方式来创建修改节点。相较于 React 的 Jsx 和 Vue 的模版,这种方法比较低效。所以,我们可以使用 Template 来解决这问题。

<template id='text-reverse'>
    <style>
        *{
            background: red;
        }
    </style>
    <span id='text'></span>
</template>
class TextReverse extends HTMLElement {
    constructor() {
        super();
        const shadowRoot = this.attachShadow({ mode: 'open' });
        const text = this.getAttribute('text') || '';
        const template = document.getElementById('text-reverse').content.cloneNode(true);
        template.getElementById('text').textContent = text.split('').reverse().join('');
        shadowRoot.appendChild(template);
    }
}

我们在 html 中定义了一个 template,然后,就和操作普通元素一样获取到 template 节点,然后深拷贝一份节点内容。最后直接操作这个节点。

Slot

VueSlot 相似,Slot 赋予了组件更高的可扩展性。通过 Slot,我们可以给组件传入更多的自定义内容。

在上面的例子中,我们给组件添加一个自定义的标题。

<text-reverse text='12345'>
    <span slot='title'>text reverse</span>
</text-reverse>
<template id='text-reverse'>
        <h1><slot name='title'>default title</slot></h1>
        <span id='text'></span>
</template>

模版中,我们定义一个 slot 元素,命名为 title,并且设置一个无内容时的默认值 default title。 使用的时候,我们在元素中添加一个 slot 属性来与模版中的 slot 相匹配。

继承现有元素

至今,我们都是完全自定义组件内容,假如我们想扩展现有系统元素,那就需要定义一个 内置自定义元素我们来用一个屏蔽数字的 p 元素来说明。

class PFilter extends HTMLParagraphElement {
    constructor() {
        super();
        const textContent = this.textContent;
        this.textContent = textContent.replace(/\d/g, '*');
    }
}
customElements.define(
    'p-filter',
    PFilter,
    {
        extends: 'p'
    }
)

我们这边不再是继承 HTMLElement,而是继承需要扩展的 p节点 HTMLParagraphElement

<p is='p-filter'>我的手机号是:10086</p>

不同于独立自定义组件,我们还是需要用原有元素名去声明,并且在 is 属性中填写我们的组件名。

生命周期

和大多数框架一样,Web Component 也含有许多控制组件生命周期的方法。

  1. connectedCallback:当 custom elemen t首次被插入 DOM 时,被调用。
  2. disconnectedCallback:当 custom element 从 DOM 中删除时,被调用。
  3. adoptedCallback:当 custom element 被移动到新的文档时,被调用。
  4. attributeChangedCallback: 当 custom element 增加、删除、修改自身属性时,被调用。

我们只需在定义组件的类中声明对应的方法即可。attributeChangedCallback 相对与别的属性比较特别,他需要 搭配 observedAttributes 使用

class TextReverse extends HTMLElement {
    //...
    static get observedAttributes () {
        return ['text'];
    }
    attributeChangedCallback () {
        const text = this.getAttribute('text') || '';
        this.shadowRoot.getElementById('text').textContent = text.split('').reverse().join('');
    }
}

我们在 observedAttributes静态方法中添加需要监听的属性值。然后,在 text 改变的时候,触发 attributeChangedCallback方法来更新 text的值。

最后

Web Component 的功能十分强大,相较于 React,Vue等框架,他天生自带样式隔离,并且最主要的是拥有浏览器的原生支持。不过,想要达到工程开发标准 的话,他还有一段很长很长的路要走