webcomponent 入门篇之实现一个简单组件

1,714 阅读5分钟

前言

此文为 webcomponent 入门篇,是对于 webcomponent 的基础介绍,并通过一个简单的组件实现例子带你无痛入门 webcomponent,轻松了解 webcomponent。

温馨提示:老手可以直接跳过此文。

webcomponent 是什么

webcomponent 直译过来就是网页组件,它能够让我们不借助第三方框架也能写出自定义的组件,通过编写自定义组件,可以在你需要的任意节点方便快捷地使用组件,并且不用担心与文档内的其他部分冲突。

webcomponent 来源已久,从 1998 年微软开创的 HTML components,经过二十多年的演变,如今的 webcomponent 技术已经逐步成熟规范。

webcomponent 的主要组成

  1. 自定义元素(Custom element

允许你定义 custom elements 及其行为,然后可以在你的用户界面中按照需要使用它们。

  1. Shadow DOM

用于将封装的 Shadow DOM 附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,你可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。

Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样。以下是它的详细解释:

  • Shadow host:一个常规 DOM节点,Shadow DOM 会被附加到这个节点上。
  • Shadow tree:Shadow DOM内部的DOM树。
  • Shadow boundary:Shadow DOM结束的地方,也是常规 DOM开始的地方。
  • Shadow root: Shadow tree的根节点。 image.png
  1. HTML template(HTML 模板)

<template> 和 <slot> 元素使你可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。 如下所示,就是一个非常简单的 html 模板:

<template> <h1>Hello world</h1> </template>

webcomponent 概念参考来自MDN

webcomponent 的生命周期

生命周期触发时机
connectedCallback当自定义元素第一次被连接到文档 DOM 时被调用。
disconnectedCallback当自定义元素与文档 DOM 断开连接时被调用。
attributeChangedCallback(attrName, oldVal, newVal)当自定义元素的一个属性被增加、移除或更改时被调用。
adoptedCallback当自定义元素被移动到新文档时被调用。

slot

与 vue 的用法几乎一模一样,可以设置默认插槽和具名插槽,如下代码所示。

  1. 自定义元素 custom-element,并设置插槽
<p><slot name="text">默认文案</slot></p>
  1. 使用插槽
<custom-element> <span slot="text">hello world!</span> </custom-element>

相对于第三方框架的优点

原生组件简单直接,符合直觉,不用加载任何外部模块,代码量小。

实现一个简单的 webcomponent

接下来,我们来一起使用 webcomponent 实现一个简单的按钮组件,样式如下图所示。

预告:整个编写过程是和 vue 非常相似的。从下面的例子我们将会体验到 vue 很大程度上参考了 webcomponent。

一、创建组件模板

使用 template 创建 html 模板,并在 template 内写组件样式,实现样式隔离。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
  </head>
  <body>
    <template id="webButtonTemplate">
      <style>
        .button {
          display: inline-flex;
          justify-content: center;
          align-items: center;
          width: 130px;
          height: 40px;
          background-color: green;
          border: 0;
          border-radius: 12px;
          padding: 10px;
          color: #fff;
          box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
          box-sizing: border-box;
        }
        .button > .button_icon {
          width: 20px;
          height: 20px;
          margin-right: 6px;
        }
        .button > .button_text {
          font-size: 14px;
        }
      </style>
      <button class="button">
        <img class="button_icon" />
        <span class="button_text"></span>
      </button>
    </template>
  </body>
</html>

二、创建一个类来指定 webcomponent 的功能

首先,因为我们 webcomponent 所有的功能都是基于 HTMLElement 扩展的,所以我们需要创建一个HTMLElement 的子类,constructor 中执行 super 方法调用父类构造器 (子类构造器中出现 this 引用之前必须先调用 super()),如下代码所示。

class WebButton extends HTMLElement {
  constructor() {
    super();
  }
}

三、将一个 Shadow DOM 附加到自定义元素上

这一步是可选的,如果你需要将组件与外部隔离,使得组件内部的代码无法影响外部,创建一个封闭的空间。那么我们需要使用自定义元素的 attachShadow 方法将一个 Shadow DOM 附加到自定义元素上。

attachShadow 方法中可以选择两种模式(mode),如下:

  1. “open” ——外部页面中的 JS 可以访问 Shadow DOM(使用Element.shadowRoot
  2. “closed” ——Shadow DOM 只能在 webcomponent 内访问。
class WebButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow( { mode: 'closed' } );
  }
}

四、组装组件

获取并克隆模板DOM,再将组件通过参数传递过来的属性写入到克隆的模板DOM之中,就可以完成一个简单的组件组装了。

class WebButton extends HTMLElement {
  constructor() {
    super();
    var shadow = this.attachShadow( { mode: 'closed' } );
    // 创建组件模板
    var templateElem = document.getElementById('webButtonTemplate');
    // 克隆模板的所有子元素(因为该模板可能会被其他自定义元素使用)
    var content = templateElem.content.cloneNode(true);
    // 获取实例上的参数,赋值到克隆出来的模板中
    content.querySelector('.button>.button_icon').setAttribute('src', this.getAttribute('icon'));
    content.querySelector('.button>.button_text').innerText = this.getAttribute('text');
    shadow.appendChild(content);
  }
}

五、注册组件

使用浏览器原生的 customElements.define() 方法,告诉浏览器元素与这个类关联,也就是我们常说的组件注册的过程。

class WebButton extends HTMLElement {
  constructor() {
    super();
    var shadow = this.attachShadow( { mode: 'closed' } );
    var templateElem = document.getElementById('webButtonTemplate');
    var content = templateElem.content.cloneNode(true);
    content.querySelector('.button>.button_icon').setAttribute('src', this.getAttribute('icon'));
    content.querySelector('.button>.button_text').innerText = this.getAttribute('text');
    shadow.appendChild(content);
  }
}
window.customElements.define('web-button', WebButton);

注意:组件注册时使用的名字一定要用短横线相连,否则浏览器将无法识别这是 webcomponent

六、组件的使用

如下所示,我们通过使用组件标签,并通过属性传递参数,开头中的两个按钮就实现了。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
  </head>
  <body>
    <web-button text="圣诞按钮" icon="https://s3.bmp.ovh/imgs/2023/12/18/260c0252c96ff831.png" bgColor="green" color="#fff"></web-button>
    <web-button text="新年按钮" icon="https://s3.bmp.ovh/imgs/2023/12/18/b52b5a1b0c7147cb.png" bgColor="red" color="yellow"></web-button>
  </body>
</html>

完整代码参考如下:

后记

文章到此结束,感谢阅读。本文是对于webcomponent的基础介绍,下一篇文章将会进一步介绍 webcomponent 的其他特性及使用方法,欢迎持续关注。

参考

Web Components 入门实例教程 - 阮一峰的网络日志

web.dev/articles/cu…

Web Components Tutorial for Beginners [2019]