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

引言

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

什么是 Web Components?

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

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

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

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

Web Components 的 3 个核心项

Web Components 由以下 3 个核心项构成:

  • Custom elements(自定义元素)

  • Shadow DOM(影子DOM)

  • HTML templates(HTML模板)

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

自定义元素

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

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

xml 复制代码
<!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 的自定义元素示例:

xml 复制代码
<!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 的另一个重要特性,它使得我们可以将组件的结构和内容分离开来,进一步增强了组件的可复用性。

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

xml 复制代码
<!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 个属性了

具体代码如下:

scss 复制代码
static get observedAttributes() {
    return ['color','label']
}

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

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

javascript 复制代码
attributeChangedCallback(activeName, oldValue, newValue) {
    if (oldValue === null || oldValue === newValue) return
    ...
}

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

xml 复制代码
<!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

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

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

以下为 Quark 的官方介绍:

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

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

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

总结

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

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

相关推荐
大前端爱好者1 小时前
React 19 新特性详解
前端
随云6321 小时前
WebGL编程指南之着色器语言GLSL ES(入门GLSL ES这篇就够了)
前端·webgl
寻找09之夏2 小时前
【Vue3实战】:用导航守卫拦截未保存的编辑,提升用户体验
前端·vue.js
非著名架构师2 小时前
js混淆的方式方法
开发语言·javascript·ecmascript
多多米10053 小时前
初学Vue(2)
前端·javascript·vue.js
敏编程3 小时前
网页前端开发之Javascript入门篇(5/9):函数
开发语言·javascript
柏箱3 小时前
PHP基本语法总结
开发语言·前端·html·php
新缸中之脑3 小时前
Llama 3.2 安卓手机安装教程
前端·人工智能·算法
hmz8563 小时前
最新网课搜题答案查询小程序源码/题库多接口微信小程序源码+自带流量主
前端·微信小程序·小程序
看到请催我学习3 小时前
内存缓存和硬盘缓存
开发语言·前端·javascript·vue.js·缓存·ecmascript