JavaScript学习笔记:Web Components
组件的概念
组件是对可重用的HTML与JS功能的封装。
在没有组件的时候,同样的HTML结构会在文档中多次出现,使代码变得复杂。
在使用组件封装后,就像使用一个HTML标签那样使用这些HTML结构,HTML文档变得清晰易维护。
它主要由三项技术实现:
-
Custom Element:
自定义元素,JavaScript WEB API。就像浏览器已经定义的那些元素一样,自定义的元素也有属性、事件等特性。 -
Shadow DOM:影子DOM,JavaScript WEB API。
影子DOM不在DOM树上,它以某个元素为宿主,附加在该元素上。
要通过其宿主的shadowRoot属性来访问它。shadowRoot是就像一个独立的DOM树,可以添加与移除元素。 -
HTML templates:
HTML的<template>标签中可以包含任何文本内容及HTML标签。
浏览器会解析其内容,但是不会将解析的内容添加到DOM树,所以其内容不会渲染。
HTMLTemplateElement对象的content属性是一个DocumentFragment对象,所有从<template>标签中解析的DOM都存放于此。
组件的生命周期
-
connectedCallback: 当自定义元素第一次被连接到文档DOM时被调用。
-
disconnectedCallback: 当自定义元素与文档DOM断开连接时被调用。
-
adoptedCallback: 当自定义元素被移动到新文档时被调用。
-
attributeChangedCallback: 当自定义元素的一个属性被增加、移除或更改时被调用。
组件的使用
组件是一个自定义的类,可以使用构造函数语法或类语法定义。
// 定义一个人员信息卡片组件
class InfoCard extends HTMLElement {
constructor() {
super();
// 将一个影子DOM附加到自定义元素
this.attachShadow({mode: 'open'});
// 为影子DOM添加元素
this.shadowRoot.append(InfoCard.template.content.cloneNode(true));
this.nameEle = this.shadowRoot.querySelector('#name');
this.genderEle = this.shadowRoot.querySelector('#gender');
this.ageEle = this.shadowRoot.querySelector('#age');
}
// 初次添加到DOM树上回调
connectedCallback () {
this.style.display = 'inline-block';
this.style.border = "1px solid #aeaeae";
this.style.width = "200px";
this.style.textAlign = "center";
}
// 被监听的属性,是一个静态的数组类型属性
static get observedAttributes () {return ['name', 'age', 'gender']}
// 被监听的属性发生变化时回调
attributeChangedCallback (name, oldVal, newVal) {
if (this.hasOwnProperty(name+'Ele')) {
this[name+'Ele'].textContent = newVal;
}
}
// 以对象属性代理元素属性
get name () {return this.getAttribute('name');}
get age () {return this.getAttribute('age');}
get gender () {return this.getAttribute('gender');}
set name (val) {return this.getAttribute('name', val);}
set age (val) {return this.getAttribute('age', val);}
set gender (val) {return this.getAttribute('gender', val);}
}
// 静态属性作为模板,可以重复使用
InfoCard.template = document.createElement('template');
InfoCard.template.innerHTML = `
<p><span id='name'></span><span id="age"></span><span id=gender></span></p>
<style>:host #name, :host #age, :host #gender {margin: 8px;}</style>
`;
// 为自定义标签名定义元素类
// html中所有指定的标签会自动实例化为该元素对象
customElements.define('info-card', InfoCard);
插槽<solt>
插槽是定义时的占位,在使用时可以使用实际的标签替换。
slot标签有一个name属性,用于为影子宿主中的阳光DOM指定插槽。
若有一个插槽,则阳光DOM会像是该插槽的子元素一样显示;
若有插槽而没有阳光元素,则该插槽照常显示;
若有多个插槽,且插槽有name属性,就可以为阳光元素指定插槽了。
定义:
<info-card>
<slot name="name"><span>Your Name</span></slot>
</info-card>
使用:
<info-card>
<h2 slot="name">TOM</h2> /*h2是影子宿主中的阳光DOM*/
</info-card>
组件的css
在影子DOM外部的CSS不会影响影子DOM内部元素的样式,同样的影子DOM中的<style>标签定义的css样式也不会影响外面的元素。
内部的CSS可以使用以下伪类:
-
:defined:
匹配任何已定义的元素,包括内置元素和使用 CustomElementRegistry.define() 定义的自定义元素。 -
:host:
选择 shadow DOM 的 shadow host,内容是它内部使用的 CSS(containing the CSS it is used inside)。 -
:host():
选择 shadow DOM 的 shadow host,内容是它内部使用的 CSS(这样您可以从 shadow DOM 内部选择自定义元素)— 但只匹配给定方法的选择器的 shadow host 元素。 -
:host-context():
选择 shadow DOM 的 shadow host,内容是它内部使用的 CSS(这样您可以从 shadow DOM 内部选择自定义元素)— 但只匹配给定方法的选择器匹配元素的子 shadow host 元素。