如何基于 WebComponents 封装 UI 组件库

共 9789字,需浏览 20分钟

 ·

2022-05-13 23:43

👆  这是第 142 篇不掺水的原创,想要了解更多,请戳下方卡片关注我们吧~

如何基于 WebComponents 封装 UI 组件库

https://www.zoo.team/article/web-components

前言

作为一名前端攻城狮,相信大家也都在关注着前端的一些新技术,近些年来前端组件化开发已为常态,我们经常把重用性高的模块抽离成一个个的组件,来达到复用的目的,这样减少了我们的维护成本,提高了开发的效率。但是都有一个缺点离不开框架本身,因为我们浏览器本身解析不了那些组件。那么有没有一种技术也可以达到这种效果呢?答案就是今天的主角 Web Components。

Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的 web 应用中使用它们。
目前 W3C 也在积极推动,并且浏览器的支持情况还不错。FireFox、Chrome、Opera 已全部支持,Safari 也大部分支持,Edge 也换成 webkit 内核了,离全面支持应该也不远了。当然社区也有兼容的解决方案 webcomponents/polyfills。

WebComponents 三要素和生命周期

Button 组件示例

首先我们就从一个最简单的 Button 组件开始,我们可以通过在组件中传入 type 来改变按钮的样式,并且动态监听了数据的变化。

// html
<cai-button type="primary"> 
  <span slot="btnText">
    按钮
  span>
cai-button>
<template id="caiBtn">
  <style>
    .cai-button {
      display: inline-block;
      padding4px 20px;
      font-size14px;
      line-height1.5715;
      font-weight400;
      border1px solid #1890ff;
      border-radius2px;
      background-color#1890ff;
      color#fff;
      box-shadow0 2px #00000004;
    }
    .cai-button-warning {
      border1px solid #faad14;
      background-color#faad14;
    }
    .cai-button-danger {
      border1px solid #ff4d4f;
      background-color#ff4d4f;
    }
  
style>
  <div class="cai-button"> <slot name="btnText">slotdiv>
template>
<script>
  const template = document.getElementById("caiBtn");
  class CaiButton extends HTMLElement {
    constructor() {
      super()
      this._type = {
        primary'cai-button',
        warning'cai-button-warning',
        danger'cai-button-danger',
      }
      // 开启 shadow dom
      const shadow = this.attachShadow({
        mode'open'
      })
      const type = this
      const content = template.content.cloneNode(true// 克隆一份 防止重复使用 污染
      // 把响应式数据挂到 this
      this._btn = content.querySelector('.cai-button')
      this._btn.className += ${this._type[type]}`
      shadow.appendChild(content)
    }
    static get observedAttributes() {
      return ['type']
    }
    attributeChangedCallback(name, oldValue, newValue) {
      this[name] = newValue;
      this.render();
    }
    render() {
      this._btn.className = `cai-button ${this._type[this.type]}`
    }
  }
  // 挂载到 window
  window.customElements.define('cai-button', CaiButton)
script>

三要素、生命周期和示例的解析

  • Custom elements(自定义元素): 一组 JavaScript API,允许您定义 custom elements 及其行为,然后可以在您的用户界面中按照需要使用它们。在上面例子中就指的是我们的自定义组件,我们通过 class CaiButton extends HTMLElement {} 定义我们的组件,通过 window.customElements.define('cai-button', CaiButton) 挂载我们的已定义组件。

  • Shadow DOM(影子 DOM ):一组 JavaScript API,用于将封装的“影子” DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。使用 const shadow = this.attachShadow({mode : 'open'}) 在 WebComponents 中开启。

  • HTML templates(HTML 模板)slot :template 可以简化生成 dom 元素的操作,我们不再需要 createElement 每一个节点。slot 则和 Vue 里面的 slot 类似,只是使用名称不太一样。

    内部生命周期函数

  • connectedCallback: 当 WebComponents 第一次被挂在到 dom 上是触发的钩子,并且只会触发一次。类似 Vue 中的 mounted React 中的 useEffect(() => {}, []),componentDidMount。

  • disconnectedCallback: 当自定义元素与文档 DOM 断开连接时被调用。

  • adoptedCallback: 当自定义元素被移动到新文档时被调用。

  • attributeChangedCallback: 当自定义元素的被监听属性变化时被调用。上述例子中我们监听了 type 的变化,使 Button 组件呈现不同状态。
    虽然 WebComponents 有三个要素,但却不是缺一不可的,WebComponents 借助 shadow dom  来实现样式隔离,借助 templates 来简化标签的操作。

在这个例子用我们使用了 slot 传入了俩个标签之间的内容,如果我们想要不使用 slot 传入标签之间的内容怎么办?

我们可以通过 innerHTML 拿到自定义组件之间的内容,然后把这段内容插入到对应节点即可。

组件通信

了解上面这些基本的概念后,我们就可以开发一些简单的组件了,但是如果我们想传入一些复杂的数据类型(对象,数组等)怎么办?我们只传入字符串还可以么?答案是肯定的!

传入复杂数据类型

使用我们上面的 Button,我们不仅要改变状态,而且要想要传入一些配置,我们可以通过传入一个 JSON 字符串

// html
"btn">
</cai-button>