初尝Web Components

前言

至于为什么写一篇Web Components的文章,原因大致是因为,目前我在做的组件库项目,一直有一些小伙伴在群里说这个东西,包括文章下面的评论也会出现建议

于是我就去了解并学习了一下,然后发现此文的文章较少,所以花费一些时间写这篇文章供大家了解学习

我也是一个初学者,新手,如果文章有什么不对的地方欢迎大家进行指出以及建议!

然后我通过Web Components的方式已经写了一些组件,我对Web Components部分会单开专栏供大家学习

那么我们开始吧~

什么是Web Components

其实Web ComponentsMDN上已经有了定义Web Component - Web API 接口参考 | MDN (mozilla.org)

MDN给出的概念是Web Component 是一套不同的技术,允许你创建可重用的定制元素(它们的功能封装在你的代码之外)并且在你的 web 应用中使用它们。

是的,Web Components并不是第三方的库,而是浏览器原生支持的。

简单拿我们的组件库举例,我们知道常见的三大框架VueReactAngular,都有属于自己的组件库,那么我们知道,大部分的组件库是没法跨框架进行使用的(当然,你可以做一些框架支持,例如在Angular中去书写Vue,但是这毕竟不是我们讨论的这个点)。

那么,我们想要实现这种跨端,我们可以采用Web Components来实现,也就是用Web Components写的组件库,在哪里都可以用。

也可能不是组件库,我们只有几个组件想进行跨端使用,那么我们都可以去使用Web Components,其实重点就是,在这个组件化的时代,Web Components想进行统一的操作

Web Components的组成

三大组成部分:

  1. Custom Elements(自定义元素):允许开发者定义自定义 HTML 元素,并定义其行为和样式。
  2. Shadow DOM(影子 DOM):允许开发者将一个元素的样式和行为封装在一个隔离的作用域内,以防止与其他元素的样式或行为冲突。
  3. HTML Templates(HTML 模板):允许开发者定义一个带有占位符slot,没错,就是类似于Vue中的插槽)的 HTML 模板,然后在需要时使用 JavaScript 动态地填充模板。

单看定义是很枯燥乏味的,我们还是通过例子来进行讲解

Web Components实战

我们就写一个最简单的Button,当然,最后我的这个Button组件,可能会是任何名字,也可能会有不同的功能,这也是我们组件化的意义

我们先看一下效果

关于怎么写呢,大家一定要多看文档,文档是很详细的,首先

我们定义出模板

其实我这里的写法顺序和Vue是很类似的,当然,大家写越多的Web Components就会发现,它和Vue是很相似的,这是因为Vue很多地方就是借鉴的Web Components

OK,我们模板大概是这种效果,这里我没用innerHTML的写法,大概是我还是Vue写习惯了哈哈哈

html 复制代码
    <template id="s-button-template">
        <style>
            /* 按钮样式 */
            button {
                display: inline-block;
                padding: 10px 20px;
                font-size: 16px;
                font-weight: bold;
                color: #fff;
                background-color: #007bff;
                border: none;
                border-radius: 5px;
                cursor: pointer;
            }

            /* 按钮悬停样式 */
            button:hover {
                background-color: #0062cc;
            }

            /* 按钮按下样式 */
            button:active {
                background-color: #005cbf;
            }
        </style>
        <button>
            <slot></slot>
        </button>
    </template>

这里的样式写法有很多哈,我们采用官网最开始给的这种简单的写法

然后slot我这里不过多解释了,跟Vue的一个道理

好的,我们的模板完事了,我们该通过JsClass的方式来创建 s-button 这个自定义元素

我们把代码放出来,然后逐行去解释

js 复制代码
        // 创建 s-button 自定义元素
        class SButton extends HTMLElement {
            constructor() {
                super();

                // 从模板中获取样式和内容
                const template = document.getElementById('s-button-template');
                const templateContent = template.content;

                // 创建 Shadow DOM
                const shadowRoot = this.attachShadow({ mode: 'open' });

                // 将模板内容复制到 Shadow DOM 中
                shadowRoot.appendChild(templateContent.cloneNode(true));
            }
        }

        // 定义 s-button 元素
        customElements.define('s-button', SButton);

然后我们去使用s-button即可

html 复制代码
    <s-button>Hello, World!</s-button>

总结

我们总结一下我们都做了什么

  1. 我们首先定义了一个模板 s-button-template,它包含了一个简单的样式和一个按钮元素。
  2. 然后,我们使用 JavaScript 创建了一个名为 SButton 的自定义元素,并在其中使用 Shadow DOM 将模板内容复制到自定义元素中。
  3. 最后,我们使用 customElements.define 方法将自定义元素注册为 s-button 元素
  4. 这样就可以在 HTML 中使用 <s-button> 标签来创建 s-button 组件了。

可能大家还是不太懂这个Shadow DOM是干什么的,我单独解释一下

Shadow DOM 起到了封装样式和行为的作用。具体来说,Shadow DOM 使得 s-button 组件的样式和行为可以被封装在一个独立的作用域内不会影响到外部页面的样式和行为。

你如果不明白,那么如果你开发过Vue的话,你肯定知道scoped,是的,是类似的

只不过实现方式是不同的

Shadow DOM 使用了一种更加底层的方式来实现作用域限定,而 Vue 中的 scoped CSS 则是通过在样式选择器中添加特定的属性选择器来实现的。不过,它们都可以达到相同的效果

计数器

我们可以封装一个计数器组件,其实很简单,就是button的基础上,加上一块计数的区域

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Components</title>
</head>

<body>
    <s-button>Add</s-button>
    <template id="s-button-template">
        <style>
            /* 按钮样式 */
            button {
                display: inline-block;
                padding: 10px 20px;
                font-size: 16px;
                font-weight: bold;
                color: #fff;
                background-color: #007bff;
                border: none;
                border-radius: 5px;
                cursor: pointer;
            }

            /* 按钮悬停样式 */
            button:hover {
                background-color: #0062cc;
            }

            /* 按钮按下样式 */
            button:active {
                background-color: #005cbf;
            }

            /* 计数器样式 */
            .counter {
                margin-top: 10px;
                font-size: 14px;
                color: #999;
            }
        </style>
        <button>
            <slot></slot>
        </button>
        <div class="counter">Counter: 0</div>
    </template>
    <script>
        // 创建 s-button 自定义元素
        class SButton extends HTMLElement {
            constructor() {
                super();

                // 从模板中获取样式和内容
                const template = document.getElementById('s-button-template');
                const templateContent = template.content;

                // 创建 Shadow DOM
                const shadowRoot = this.attachShadow({ mode: 'open' });

                // 将模板内容复制到 Shadow DOM 中
                shadowRoot.appendChild(templateContent.cloneNode(true));

                // 获取计数器元素
                this.counterElement = shadowRoot.querySelector('.counter');

                // 初始化计数器值为0
                this.counter = 0;

                // 添加点击事件处理函数
                this.shadowRoot.querySelector('button').addEventListener('click', () => {
                    this.incrementCounter();
                });
            }

            // 增加计数器值并更新显示
            incrementCounter() {
                this.counter++;
                this.updateCounter();
            }

            // 更新计数器显示
            updateCounter() {
                this.counterElement.textContent = `Counter: ${this.counter}`;
            }
        }

        // 定义 s-button 元素
        customElements.define('s-button', SButton);
    </script>
</body>

</html>

这里就是意思说,我们可以通过正常的事件的方式来给组件加功能

然后我们就会得到对应的效果

Web Components生命周期

  1. connectedCallback():在元素被插入到文档中时调用,可以进行一些初始化操作,比如添加事件监听器或从外部资源加载数据。
  2. disconnectedCallback():在元素从文档中移除时调用,可以进行一些清理操作,比如移除事件监听器或取消正在进行的网络请求。
  3. attributeChangedCallback(attributeName, oldValue, newValue):当元素的属性被添加、移除、更新或替换时调用,可以对属性的变化作出响应。
  4. adoptedCallback():当元素从一个文档转移到另一个文档(例如通过document.importNode()方法)时被调用。

我们可以通过上面的Button的例子,来应用一下生命周期

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Components</title>
  </head>
  <body>
    <s-button>Add</s-button>

    <template id="s-button-template">
      <style>
        /* 按钮样式 */
        button {
          display: inline-block;
          padding: 10px 20px;
          font-size: 16px;
          font-weight: bold;
          color: #fff;
          background-color: #007bff;
          border: none;
          border-radius: 5px;
          cursor: pointer;
        }

        /* 按钮悬停样式 */
        button:hover {
          background-color: #0062cc;
        }

        /* 按钮按下样式 */
        button:active {
          background-color: #005cbf;
        }

        /* 计数器样式 */
        .counter {
          margin-top: 10px;
          font-size: 14px;
          color: #999;
        }
      </style>
      <button>
        <slot></slot>
      </button>
      <div class="counter">Counter: 0</div>
    </template>

    <script>
      // 创建 s-button 自定义元素
      class SButton extends HTMLElement {
        constructor() {
          super();

          // 创建 Shadow DOM
          const shadowRoot = this.attachShadow({ mode: 'open' });

          // 从模板中获取样式和内容
          const template = document.getElementById('s-button-template');
          const templateContent = template.content;

          // 将模板内容复制到 Shadow DOM 中
          shadowRoot.appendChild(templateContent.cloneNode(true));

          // 获取计数器元素
          this.counterElement = shadowRoot.querySelector('.counter');

          // 初始化计数器值为0
          this.counter = 0;

          // 添加点击事件处理函数
          this.shadowRoot.querySelector('button').addEventListener('click', () => {
            this.incrementCounter();
          });
        }

        // 元素被插入到文档中时调用
        connectedCallback() {
          console.log('SButton connected to the DOM');
        }

        // 元素从文档中移除时调用
        disconnectedCallback() {
          console.log('SButton removed from the DOM');
        }

        // 元素的属性值发生变化时调用
        attributeChangedCallback(name, oldValue, newValue) {
          console.log(`SButton attribute "${name}" changed from "${oldValue}" to "${newValue}"`);
        }

        // 监听的属性列表
        static get observedAttributes() {
          return ['disabled'];
        }

        // 增加计数器值并更新显示
        incrementCounter() {
          this.counter++;
          this.updateCounter();
        }

        // 更新计数器显示
        updateCounter() {
          this.counterElement.textContent = `Counter: ${this.counter}`;
        }
      }

      // 定义 s-button 元素
      customElements.define('s-button', SButton);
    </script>
  </body>
</html>

具体来说,我们在 SButton 类中实现了以下生命周期方法:

  • connectedCallback(): 当组件被插入到文档中时调用。在这个例子中,我们在这个方法中使用 console.log() 打印了一条消息,以显示组件被添加到 DOM 中的事件。
  • disconnectedCallback(): 当组件从文档中移除时调用。在这个例子中,我们在这个方法中使用 console.log() 打印了一条消息,以显示组件被从 DOM 中移除的事件。
  • attributeChangedCallback(name, oldValue, newValue): 当组件所监听的属性值发生变化时调用。在这个例子中,我们监听了 disabled 属性,并在这个方法中使用 console.log() 打印了一条消息,以显示该属性值的变化。

此外,我们还定义了一个 observedAttributes 静态方法,它返回一个数组,用于指定组件需要监听的属性列表。在这个例子中,我们监听了 disabled 属性。

Web Components的样式

除了上面我们说过的Shadow DOM可以将组件的样式和行为可以被封装在一个独立的作用域内之外,书写Web Components的样式会使用var() 函数来引用 CSS 变量

还是拿我们的Button举例,把我们的样式通过css变量来进行一些改造

html 复制代码
<template id="s-button-template">
  <style>
    /* 定义 CSS 变量 */
    :host {
      --button-bg-color: #007bff;
      --button-text-color: #fff;
      --button-border: none;
    }

    /* 按钮样式 */
    button {
      display: inline-block;
      padding: 10px 20px;
      font-size: 16px;
      font-weight: bold;
      color: var(--button-text-color); /* 使用 CSS 变量 */
      background-color: var(--button-bg-color); /* 使用 CSS 变量 */
      border: var(--button-border); /* 使用 CSS 变量 */
      border-radius: 5px;
      cursor: pointer;
    }

    /* 按钮悬停样式 */
    button:hover {
      background-color: #0062cc;
    }

    /* 按钮按下样式 */
    button:active {
      background-color: #005cbf;
    }

    /* 计数器样式 */
    .counter {
      margin-top: 10px;
      font-size: 14px;
      color: #999;
    }
  </style>
  <button>
    <slot></slot>
  </button>
  <div class="counter">Counter: 0</div>
</template>

Lit

Lit 是一个基于 Web Components 标准的 JavaScript 库,它提供了一些工具和功能,用于简化 Web Components 的开发。

Lit官网

很简单,Web ComponentsLit的关系,我觉得就像JavaScriptJquery

我们可以通过Lit进行代码的简化

html 复制代码
 // 定义组件的属性和默认值
            static properties = {
                counter: { type: Number },
            };

            constructor() {
                super();
                this.counter = 0;
            }

            // 渲染组件的模板
            render() {
                return html`
                    <button @click=${this.incrementCounter}>
                        <slot></slot>
                    </button>
                    <div class="counter">Counter: ${this.counter}</div>
                `;
            }

            // 增加计数器值并更新显示
            incrementCounter() {
                this.counter++;
                this.requestUpdate();
            }

这里

  • 我们通过 @event 装饰器来监听 Web Components 的事件
  • 使用了 Lit 提供的属性定义语法,定义了 counter 属性的类型为 Number
  • 在组件的 render 方法中,我们使用了 Lit 的模板渲染语法,使用 html 模板函数来定义组件的模板。在模板中,我们使用了 ${} 语法来将 counter 属性的值绑定到计数器显示的文本中,从而实现了数据绑定

当然Lit还有很多别的功能,这里就是介绍一下这个库

尾语

这篇只是介绍一下Web Components,我们并没有进行实战

我觉得这部分知识点是蛮有趣的,所以我应该会单开专栏来讲,同时我们的组件库项目,我也在研究如何更好地应用Web Components在我们的组件库中

当然,现在已经有很多组件库开始应用Web Components了,也欢迎大家去了解

我觉得Web Components我们可以作为一个知识点去学习,而不是去研究,vue、react等框架是否被淘汰的问题,希望大家仁者见仁智者见智~

当然,欢迎大家提出意见,以及补充~

相关推荐
Python私教16 分钟前
Go语言现代web开发15 Mutex 互斥锁
开发语言·前端·golang
A阳俊yi16 分钟前
Vue(13)——router-link
前端·javascript·vue.js
好看资源平台30 分钟前
前端框架对比与选择:如何在现代Web开发中做出最佳决策
前端·前端框架
4triumph32 分钟前
Vue.js教程笔记
前端·vue.js
程序员大金1 小时前
基于SSM+Vue+MySQL的酒店管理系统
前端·vue.js·后端·mysql·spring·tomcat·mybatis
清灵xmf1 小时前
提前解锁 Vue 3.5 的新特性
前端·javascript·vue.js·vue3.5
白云~️1 小时前
监听html元素是否被删除,删除之后重新生成被删除的元素
前端·javascript·html
金灰1 小时前
有关JS下隐藏的敏感信息
前端·网络·安全
Yxmeimei1 小时前
css实现居中的方法
前端·css·html
6230_1 小时前
git使用“保姆级”教程2——初始化及工作机制解释
开发语言·前端·笔记·git·html·学习方法·改行学it