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 元素。

posted @ 2023-06-24 15:01  钰琪  阅读(42)  评论(0编辑  收藏  举报