Web Component

  目录

Web Component初探

什么是 Web Component

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

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

例子

我们准备编写一个 TextReverse 组件,TextReverse 只有一个很简单的功能,就是把传入的 字符串颠倒显示。
例如: 将会显示 321。
第一步,我们需要 定义 这个自定义组件。

1
2
3
4
5
6
7
8
9
10
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。
定义完成之后,我们要告知一下系统,也就是 组件注册。

1
2
3
4
customElements.define(
'text-reverse',
TextReverse
)

这里有一个小细节,就是我们注册的名字必须是带短横线的。
注册完成之后就可以正式使用啦。

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

上面的例子中,我们用到了 shadow root,他承载着我们组件所有的内容。而他也是 Web Component 核心技术。
我们都知道 Dom 其实就是一棵树,而我们的组件则是树上的一个节点。我们可以称组件节点为 shadow host。
shadow host 中含有一颗与外界隔离的 dom 树,我们称之为 shadow tree。shadow tree 中的内容不会影响到外界。Shadow Root 则是这一课shadow tree 的根节点。

样式隔离

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

1
2
3
4
5
6
7
8
9
10
11
12
class TextReverse extends HTMLElement {
constructor() {
super();
// ...
const style = document.createElement('style');
style.textContent = `* {
background: red;
}`
shadowRoot.appendChild(style);
// ...
}
}

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

Template

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<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,我们可以给组件传入更多的自定义内容。
在上面的例子中,我们给组件添加一个自定义的标题。

1
2
3
4
5
6
7
<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 元素来说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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。

1
<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 使用。

1
2
3
4
5
6
7
8
9
10
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等框架,他天生自带样式隔离,并且最主要的是拥有浏览器的原生支持。不过,想要达到工程开发标准 的话,他还有一段很长很长的路要走。