什么是 Web Component
Web Component 是一种 W3C标准 支持的 组件化方案,通过它,我们可以编写可复用的 组件,同时,我们也可以对自己的组件做更精细化的控制。正如 PWA 一样,他并非一项单一的技术,而是由三项技术组成:
- Shadow DOM
- Custom elements
- 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 元素中。
最后,我们把带有 text 的 span 塞入 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
和 Vue 的 Slot 相似,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 也含有许多控制组件生命周期的方法。
- connectedCallback:当 custom elemen t首次被插入 DOM 时,被调用。
- disconnectedCallback:当 custom element 从 DOM 中删除时,被调用。
- adoptedCallback:当 custom element 被移动到新的文档时,被调用。
- 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等框架,他天生自带样式隔离,并且最主要的是拥有浏览器的原生支持。不过,想要达到工程开发标准 的话,他还有一段很长很长的路要走。