# Web Components 全揽

6次阅读

共计 4413 个字符,预计需要花费 12 分钟才能阅读完成。

Web Components 全揽
Web Components 技术可以把一组相关的 HTML、JS 代码和 CSS 风格打包成为一个自包含的组件,只要使用大家熟悉的标签即可引入此组件。Web Components 技术包括:

Custom Element
Shadow DOM
Template
HTML Import

四个分离而又互相关的四个构造块。其中核心的即使是 Custom Element、Shadow DOM,顺便会讲到而 Template 是一个支持技术。HTML Import 曾经被 Chrome 加入但是随后和 V0 一起被废弃。这里也不会讨论它。
Custom Element 定制元素。
定制元素可以在原生元素外创建定制元素。定制元素是 Web 组件的一个基本构成块。可以在一个 js 文件内包含 Custom Element 需要的全部要素,包括 HTML 模板、CSS Style 和 ES6 类。并使用一个 HTML 文件,引用此 js 文件从而可以使用定制元素。
假设我们创建 Spin Button,定制元素标签为:

<spin-button value=“100”step=”10″ min=”50″ max=”150″></spin-button>

我们首先实现此定制元素,但是为了简单起见,晚一点才看它的属性。此定制元素内部有一个加号按钮,一个减号按钮,一个 span 显示当前值。那么只需要把这个 HTML 模板组织、风格和代码组合在一个文件内:
var template = `
<button inc>+</button><span>1</span><button dec>-</button>
<style>
span{color:red;}
*{font-size:2rem;}
</style>
`
class SpinButton extends HTMLElement{
connectedCallback(){
this.innerHTML = template
var b1 = this.querySelector(‘[inc]’)
var b2 = this.querySelector(‘[dec]’)
var s = this.querySelector(‘span’)
var i = 1
b1.onclick = function(){
s.innerHTML = i++
}
b2.onclick = function(){
s.innerHTML = i–
}
}
}
customElements.define(‘spin-button’,SpinButton)

并且创建一个 index.html 文件加载此文件,即可使用新的定制元素 spin-button 了:
<script src=”./spin.js”></script>
<spin-button></spin-button>

你可以看到执行在浏览器内的界面上的两个按钮和一个 span。创建一个定制元素有几个要点:

新的 JS 定制类需要继承于类 HTMLElement
回调 connectedCallback 提供一个生命周期事件,当定制元素成功挂接到 DOM 后,会调用此回调,可以在此回调代码内加入自己的定制内容
代码中的 this,指向了此定制元素本身,因此可以通过 this.innerHTML 设置本定制元素的内部 DOM

这样,我们创建了一个独特的定制元素,这个元素不在原生的浏览器标签内。
定制元素就是这样创建了,并且对于使用者来说,只要通过熟悉的元素标签,即可引用一组带有定制风格、操作和界面的组件了。
但是此时的定制元素有一个问题,就是它内部定义的风格,不仅仅会影响内部的元素,也会泄露到外部导致文档也被影响,从而引发我们不希望的边际效应。比如在 index.html 内如果在文件尾部加入这样的文本:
<span>black</span>

你会发现 black 文本不是默认的颜色,而是红色,这样红色来自于定制元素内部的风格定义代码。如果希望隔离组件内的风格定义,那么可以使用 Shaddow DOM 技术。此主题会在下一部分内介绍。
Shadow DOM
Web 建站使用组件技术有比较长的历史了,这个技术一直以来都有一个挑战,就是如何让一个页面可以使用第三方控件,但是不会被此组件使用的 CSS 风格所影响。解决方案是 CSS 可以局部化。想要组件内部的风格不会影响到外部,办法就是使用 Shadow DOM。Shadow DOM 创建了一个隔离区,在这个隔离区内的 DOM 是独立的,这意味着:

内部 DOM Tree 不会被外部文档访问到
也不会被外部的风格设置影响
内部的风格也不会影响到外部文档

我们拿前一个案例代码做实验,看看如果使用这个技术特性。
使用 Shadow DOM 的关键,是首先创建一个 Shadow Node,整个组件内部的 HTML 片段都插入到此节点内,而不是直接使用组件的 innerHTML。我们可以在组件对象的构造器内执行此代码:
class SpinButton extends HTMLElement{
constructor(){
super()
var shadow = this.attachShadow({mode:’open’})
var t = document.createElement(‘template’)
t.innerHTML = template
shadow.appendChild(t.content.cloneNode(true))
}
}

执行后,你会发现 span 的风格不再影响组件之外的标签。看起来还是很简单的,只要把你本来需要构造的 HTML 内部 DOM 插入到 shadow 节点内即可。
定制元素的属性
元素的属性被称为 Attribute,JS 对象内的属性被称为 Property。代码惯例上每一个 Attribute 都会有 JS 对象的一个 Property 对应。为了方便,我们希望添加的 Attribute 可以和 JS 内的 Property 同步。就是说,如果有人通过 HTML DOM API 修改了 Attribute,那么我希望对于的 JS 属性会被同步修改;反之亦然,有人修改了 Property,那么这个修改可以会同步修改到对应的 Attribute。
我们以 spin-button 的 value 属性为例。定义一个普通的 Property 的方法是通过 get/set 关键字,比如定义 value:
get value(){}
set value(newValue){}

随后就可以使用 object.value 访问此属性值,或者通过 object.value = newValue 为属性设置新值。可以在两个函数内通过代码设置和 Attribute 同步:
get value(){
return this.getAttribute(‘value’) || 1
}
set value(v){
this.setAttribute(‘value’,v)
}

这样代码内通过对属性 value 的访问,最后都会导致对 Attribute 的访问。如果有代码对 Attribute 访问,如何修改 Attribute 的同时同步更新 Property 呢。这就需要利用 HTMLElement 提供的生命周期方法了:
static get observedAttributes() {
return [‘value’];
}
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case ‘value’:

break;
}
}

方法 observedAttributes 听过返回值声明需要观察的属性,这样就可以在指定属性清单发生更新时通过另一个生命周期方法 attributeChangedCallback, 通知代码变化的情况。做响应的同步处理。整合后的代码如下:
var template = `
<button inc>+</button><span>1</span><button dec>-</button>
<style>
span{color:red;}
*{font-size:2rem;}
</style>
`
class SpinButton extends HTMLElement{
constructor(){
super()
var shadow = this.attachShadow({mode:’open’})
var t = document.createElement(‘template’)
t.innerHTML = template
shadow.appendChild(t.content.cloneNode(true))
var b1 = shadow.querySelector(‘[inc]’)
var b2 = shadow.querySelector(‘[dec]’)
this.s = shadow.querySelector(‘span’)
var i = 1
var that = this
b1.onclick = function(){
that.s.innerHTML = ++that.value
}
b2.onclick = function(){
that.s.innerHTML = — that.value
}
}
static get observedAttributes() {
return [‘value’];
}
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case ‘value’:
this.s.innerHTML = newValue
break;
}
}
get value(){
return this.getAttribute(‘value’) || 1
}
set value(v){
this.setAttribute(‘value’,v)
}
}
customElements.define(‘spin-button’,SpinButton)

状态
Web Components 的关键构成技术包括 Custom Element 和 Shadow DOM,最早在 Chrome 实现,第一个版本被称为 V0 但是其他浏览器没有跟进,因此逐步被废弃。本文讨论的是 V1 版本。Firefox 也已经实现了 V1 版本。可以在网站 Whatcaniuse 查询当前支持状态。
ref
Posts of wb
https://alligator.io/web-comp…
Custom Elements v1: Reusable Web Components
https://developers.google.com…*3. web-components-exampleshttps://github.com/mdn/web-co…
Firefox 63 – Tricks and Treats!
https://hacks.mozilla.org/201…
HTML Web Component using Plain JavaScript
https://www.codementor.io/ayu…6. Doing something with Web Componentshttps://medium.com/@dalaidunc…

正文完
 0