likes
comments
collection
share

前端需要懂的知识- Web Components 的使用

作者站长头像
站长
· 阅读数 9

引言

前端需要懂的知识- Web Components 的使用 Web Components 是一种用于构建可重用和独立组件的技术,它能够提供更高级别的封装和抽象,使得开发者可以更加方便地构建和维护 Web 应用程序。今天,我就来浅谈一下 Web Components 的使用,并附上一些具体的代码示例,希望能为大家带来一点乐趣和启发。

什么是 Web Components?

前端需要懂的知识- Web Components 的使用 在开始讨论 Web Components 之前,我们先搞清楚什么是组件。组件是指具有独立功能、可复用的模块化单元,它们能够将不同的功能和样式封装起来,以便在多个项目中重复使用。

Web Components 就是一套标准,由一系列不同的技术组成,包括自定义元素、影子 DOM 和模板等。通过使用这些标准,我们可以创建独立的、可组合的组件,它们能够跨浏览器和框架进行复用。

Web Components,大体上我们将分成 2 个部分来讲解:

  • Web Components 的 3 个核心项
  • Web Components 组件的 4 个生命周期函数

Web Components 的 3 个核心项

前端需要懂的知识- Web Components 的使用 Web Components 由以下 3 个核心项构成:

  • Custom elements(自定义元素)

  • Shadow DOM(影子DOM)

  • HTML templates(HTML模板)

以上 3 个核心项和 React 类组件都是可以一一对应的。

自定义元素

自定义元素是 Web Components的基础,它允许我们创建自定义的 HTML 元素,使其具备特定的功能和样式。定义自定义元素非常简单,只需要扩展 HTMLElement 类即可。

下面是一个简单的自定义元素示例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Web Components Example</title>
</head>
<body>
  <my-custom-element></my-custom-element>

  <script>
    class MyCustomElement extends HTMLElement {
      constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = '<p>Hello, Web Components!</p>';
      }
    }

    customElements.define('my-custom-element', MyCustomElement);
  </script>
</body>
</html>

在上面的示例中,我们创建了一个自定义元素 my-custom-element,它会在被插入到文档中时显示一段文字。通过调用 customElements.define 方法,我们将自定义元素注册到浏览器,并指定了对应的类。

影子 DOM

影子 DOM 是 Web Components 中另一个重要概念,它允许我们将样式和结构封装在组件内部,防止其对外部造成影响。通过使用影子 DOM,我们可以确保组件的样式不会与全局样式冲突,从而提高组件的可维护性和复用性。

下面是一个使用影子 DOM 的自定义元素示例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Web Components Example</title>
  <style>
    my-custom-element {
      display: block;
      border: 1px solid #ccc;
    }

    my-custom-element p {
      color: red;
    }
  </style>
</head>
<body>
  <my-custom-element></my-custom-element>

  <script>
    class MyCustomElement extends HTMLElement {
      constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = `
          <style>
            :host {
              display: block;
              border: 1px solid #ccc;
            }

            p {
              color: red;
            }
          </style>
          <p>Hello, Web Components!</p>
        `;
      }
    }

    customElements.define('my-custom-element', MyCustomElement);
  </script>
</body>
</html>

在上面的示例中,我们在组件的 shadowRoot 中定义了组件的样式。注意到在样式选择器中使用了 :host,它表示组件自身。

使用影子 DOM 可以确保组件的样式只会应用在组件内部,不会对全局样式造成干扰,从而增加了组件的可靠性和可维护性。

模板

模板是 Web Components 的另一个重要特性,它使得我们可以将组件的结构和内容分离开来,进一步增强了组件的可复用性。

下面是一个使用模板的自定义元素示例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Web Components Example</title>
</head>
<body>
  <my-custom-element></my-custom-element>

  <template id="my-custom-element-template">
    <style>
      :host {
        display: block;
        border: 1px solid #ccc;
      }

      p {
        color: red;
      }
    </style>
    <p>Hello, Web Components!</p>
  </template>

  <script>
    class MyCustomElement extends HTMLElement {
      constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        const template = document.getElementById('my-custom-element-template');
        const content = template.content.cloneNode(true);
        this.shadowRoot.appendChild(content);
      }
    }

    customElements.define('my-custom-element', MyCustomElement);
  </script>
</body>
</html>

在上面的示例中,我们使用 <template> 元素定义了组件的结构和样式,然后通过 cloneNode 方法将内容克隆到组件的 shadowRoot 中。

使用模板可以使得组件的结构和样式与实际的 HTML 代码分离开来,使得组件更加易于维护和复用。

Web Components 组件的 4 个生命周期函数

这里实际上是套用了 React/Vue 组件中的 生命周期函数 名称,准确来说应该称呼为:生命周期回调函数

原生组件的 4 个生命周期函数:

  • connectedCallback:当组件第一次被添加到 DOM 文档后调用

  • disconnectedCallback:当组件从 DOM 文档移除后调用

  • adoptedCallback:当组件在 DOM 文档中被移动到其他 DOM 节点时调用

  • attributeChangedCallback:当组件的某个属性发生改变后调用

    这里的属性改变 包含:新增、移除、修改属性值 这 3 种情况

各个生命周期函数用途:

和我们平时在开发 React/Vue 组件时,一些常规的用途几乎相同。

例如 ,当某组件从 DOM 中移除,但组件本身此时并没有销毁,那就可以在 disconnectedCallback 函数中添加一些销毁 或 取消侦听操作。

这里重点说一下:connectedCallback 和 attributeChangedCallback

我们上面举得示例中,都是直接将组件 <color-button> 添加到 body 内,但如果是靠 JS 来动态添加和修改组件属性,那么就需要用到组件的生命周期回调函数了。

connectedCallback:

对于有些场景, JS 动态生成添加的自定义组件,在其类的构造函数中是无法通过 this.getAttribute() 获取属性值的,我们只能将这部分代码移动到 connectedCallback 回调函数中。

换句话说,在有些场景中,我们不再在类组件的构造函数中创建和添加 DOM 元素,而是改为在 connectedCallback() 中添加。

我在实际的项目中就遇到过这种情况,但不是说 100% 一定会出现这样的情况。

上面给这么多文字添加了加粗,实际上就是希望你能注意到。

因为我当初遇到了,查了很久才找到这样的解决办法。

attributeChangedCallback:

该生命周期回调函数的用法比其他的稍微特殊一点,因为它还需要一个配套的属性名监听函数

以 color-button 组件为例,我们需要监控 color 和 label 这 2 个属性,那么我们需要额外做的是:

  • 在类组件中,重写它的静态方法 observedAttributes()

  • 之后,就可以在类组件的 attributeChangedCallback 函数中正确监控这 2 个属性了

具体代码如下:

static get observedAttributes() {
    return ['color','label']
}

attributeChangedCallback(activeName, oldValue, newValue) {
    if(activeName === 'color'){
        ...
    }else if(activeName === 'label'){
        ...
    }
}

有些时候,为了避免组件初始化时的一些不必要监听,可以在 attributeChangedCallback 内部增加一些排除。

attributeChangedCallback(activeName, oldValue, newValue) {
    if (oldValue === null || oldValue === newValue) return
    ...
}

最后,我们用 JS 演示一下如何动态添加自定义组件。

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Components Sample</title>
    <script defer src="./color-button/index.js"></script>
</head>

<body>
    <script>
        const arr = [
            { color: 'red', label: 'Red' },
            { color: 'yellow', label: 'Yellow' },
            { color: 'green', label: 'Green' },
            { color: '#336699', label: '#336699' }
        ]

        const mydiv = document.createElement('div')
        arr.forEach((item) => {
            const colorButton = document.createElement('color-button')
            colorButton.setAttribute('color', item.color)
            colorButton.setAttribute('label', item.label)
            mydiv.appendChild(colorButton)
        })
        
        document.body.appendChild(mydiv)

    </script>
</body>

</html>

Web Components 适用场景

如果从开发组件的便捷度来讲,我个人觉得,目前 Web Components 也就是达到了 React 类组件的 60% 功能。

所以 Web Components 目前根本无法代替 React/Vue 。

但是以下 2 个场景,挺适合 Web Components 的。

  • 场景1:对老的原生 HTML 项目的改造。

    一些老的原生 html 项目如果想整体改造成 React/Vue 成本或许很大,但是局部地方可以改造成 Web Components,即方便又不至于成本很大,是个不错的方案。

  • 场景2:编写一个同时可以用在 原生 HTML、React 和 Vue 中的组件

    React 和 Vue 目前都支持 Web Components,所以 Web Components 确实可以做到一套组件代码同时运行在不同前端框架中。

    由于 Web Components 本质上就是原生 HTML,那么理论上除 React/Vue 以外其他任何前端框架也都是会支持。

基于 Web Components 的第三方组件库:Quark

前端需要懂的知识- Web Components 的使用

目前比较出名的是 哈啰 公司开源的 Quark 组件库。

Quark 组件库官网:quark-design.hellobike.com/

以下为 Quark 的官方介绍:

Quark 是一款基于 Web Components 的跨框架 UI 组件库。 它可以同时在任意框架或无框架中使用。

我使用过 Quark 组件,实话实说,Quark 组件在某些功能细节方面比不了 Antd。

我个人觉得 Quark 组件也就达到了 Antd 的 70%,但这已经很了不得了。

总结

前端需要懂的知识- Web Components 的使用 通过使用 Web Components,我们可以创建独立、可复用的组件,提高 Web 应用程序的开发效率和可维护性。本文简单介绍了 Web Components 的基本概念,并提供了一些具体的代码示例。

希望本文对大家理解和使用 Web Components 起到一定的帮助作用。当然,这只是冰山一角,Web Components 还有很多强大的功能和特性等待我们去探索。相信通过不断地学习和实践,我们能够更好地应用 Web Components 技术,构建出更加优秀的 Web 应用程序。