当前位置: 首页 > news >正文

「JavaScript深入」Web Components:构建可重用的跨框架组件

Web Components

    • 什么是 Web Components?
    • Web Components 的三大核心技术
      • 1. Custom Elements(自定义元素)
        • 定义和注册自定义元素
        • 生命周期回调
      • 2. Shadow DOM(影子 DOM)
        • 封装与隔离
        • Shadow DOM 的模式
      • 3. HTML Templates(HTML 模板)
        • 模板定义与克隆
        • 在 Web Components 中使用模板
    • 使用 Slot 支持外部内容插入
    • 属性支持与监听
    • 自定义事件
    • Web Components 组件库:Lit
    • 为什么 Web Components 没有流行?
    • Web Components 的应用场景
    • 结论

在现代 Web 开发中,组件化开发已经成为一种主流趋势。无论是 React、Vue 还是 Angular,组件化开发都极大地提高了代码的可维护性和可重用性。然而,这些框架的组件通常只能在特定的框架生态中使用,跨框架的组件复用一直是一个挑战。Web Components 的出现为这一问题提供了一个原生解决方案。下面将深入探讨 Web Components 的核心概念、技术细节以及其在实际开发中的应用。


什么是 Web Components?

Web Components 是一组由浏览器原生支持的 Web 标准,允许开发者创建可重用、封装的自定义 HTML 元素。这些元素具有跨框架兼容性,可以无缝集成到原生 HTML、React、Vue 和 Angular 等各种 Web 开发环境中。Web Components 的核心目标是通过标准化的方式实现组件的封装和复用,从而减少对特定框架的依赖。


Web Components 的三大核心技术

Web Components 的实现依赖于以下三大核心技术:

  1. Custom Elements(自定义元素)
  2. Shadow DOM(影子 DOM)
  3. HTML Templates(HTML 模板)

接下来,我们将逐一详细介绍这些技术。

1. Custom Elements(自定义元素)

Custom Elements 允许开发者定义自定义的 HTML 标签,并为其附加特定的行为。这些自定义标签可以像原生 HTML 元素一样使用,并接受属性和事件。

定义和注册自定义元素
  • 定义元素: 定义一个 JavaScript 类,该类继承自 HTMLElement,并在其中定义元素的特定行为

  • 注册元素: 通过 customElements.define() 方法注册自定义标签,并将其与定义的类绑定

    class MyElement extends HTMLElement {
      constructor() {
        super();
        // 初始化逻辑
      }
    
      connectedCallback() {
        this.innerHTML = `<p>Hello, World!</p>`;
      }
    }
    
    // 注册自定义元素
    customElements.define('my-element', MyElement);
    

    👆 我们定义了一个名为 my-element 的自定义元素,并在 connectedCallback 生命周期钩子中设置了其内容。当该元素被插入到 DOM 中时,connectedCallback 方法会被调用,从而渲染出 <p>Hello, World!</p>

生命周期回调

Custom Elements 提供了一套生命周期回调方法,允许开发者在组件的不同生命周期阶段执行特定的代码:

  • connectedCallback:当元素被插入到 DOM 中时调用,适合执行设置默认属性、启动数据获取、设置事件监听器等操作。
  • disconnectedCallback:当元素从 DOM 中移除时调用,适合执行清理工作,例如移除事件监听器、停止定时器等。
  • attributeChangedCallback:当元素的属性增加、被移除或更改时调用。要使用此回调,必须定义 static get observedAttributes() 方法。
  • adoptedCallback:当元素从一个文档被移动到另一个文档时调用,这个情况在一般的 Web 应用中较少发生,常见于与多个文档交互的复杂应用。

2. Shadow DOM(影子 DOM)

Shadow DOM 提供了一种将组件的样式和结构进行封装和隔离的方法。通过使用 Shadow DOM,组件可以避免样式污染或被外部影响。

封装与隔离

Shadow DOM 为组件创建了一个私有的子 DOM 树,外部页面的 CSS 不会影响到其样式。这种封装机制使得组件的样式和行为可以完全独立于外部环境。

class MyElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>
        p {
          color: blue;
        }
      </style>
      <p>Hello, Shadow DOM!</p>
    `;
  }
}

customElements.define('my-shadow-element', MyElement);

👆 我们创建了一个带有 Shadow DOM 的自定义元素。attachShadow({ mode: 'open' }) 方法创建了一个开放的 Shadow DOM,外部 JavaScript 可以通过 element.shadowRoot 访问和操作 Shadow DOM 中的内容。

Shadow DOM 的模式
  • open:创建的 Shadow DOM 可以通过 JavaScript 直接访问和操作。
  • closed:在某些场景中,开发者可能需要更高程度的封装和安全性,不希望外部代码访问和操作 Shadow DOM。在这种情况下可以选择隔离模式。

👇 当 mode 被设置为 open 时,创建的 Shadow DOM 可以通过 JavaScript 直接访问和操作

// 创建一个影子 DOM
const shadow = this.attachShadow({ mode: 'open' });
console.log(this.shadowRoot); // 访问影子 DOM

👇 当 mode 被设置为 closed 时,样式和模板可以被完全封装,直至需要公开的属性

const shadow = this.attachShadow({ mode: 'closed' });
console.log(this.shadowRoot); // 这将输出 null

3. HTML Templates(HTML 模板)

HTML Templates 允许开发者在页面中定义不会立即渲染的模板内容,这些内容可以在需要时通过 JavaScript 克隆和使用。

模板定义与克隆
  • 模板定义:<template> 元素中定义模板,不显示在文档但可以进行克隆

    <template id="my-template">
      <p>This is a template.</p>
    </template>
    

    👆 定义了一个 元素,其内容不会直接显示在页面上。

  • 内容克隆: 使用 document.importNode()template.content.cloneNode(true) 方法克隆模板内容,并将其插入到 DOM 中。

    方法 1: 使用 document.importNode()

    <script>
      const template = document.getElementById('my-template');
      const clone = document.importNode(template.content, true); // 深度克隆模板内容
      document.body.appendChild(clone); // 将克隆内容插入到 DOM 中
    </script>
    

    方法 2: 使用 template.content.cloneNode(true)

    <template id="my-template">
      <style>p { color: blue; }</style>
      <p>This is a template paragraph!</p>
    </template>
    
    <script>
      const template = document.getElementById('my-template');
      const content = template.content.cloneNode(true); // 深度克隆模板内容
      document.body.appendChild(content); // 将克隆内容插入到 DOM 中
    </script>
    

    👆 两种方法都可以实现模板内容的克隆和插入,cloneNode(true) 是更常用的方式。

  • 注意:

    1. <template> 元素的内容是惰性的:其中的脚本不会执行,图片不会加载,直到被插入到 DOM 中。
    2. cloneNode(true) 的参数true 表示深度克隆,包括所有子节点;false 表示只克隆当前节点。
    3. 样式和脚本的作用域:克隆后的内容会继承当前文档的样式和脚本环境。
在 Web Components 中使用模板
class MyElement extends HTMLElement {
  constructor() {
    super();
    const template = document.getElementById('my-template');
    const clone = document.importNode(template.content, true);
    this.attachShadow({ mode: 'open' }).appendChild(clone);
  }
}

customElements.define('my-template-element', MyElement);

👆 将模板内容克隆并插入到 Shadow DOM 中,从而实现了组件的封装和复用。


使用 Slot 支持外部内容插入

通过 slot 元素,Web Components 可以接受外部内容的占位符,将外部内容插入到组件的特定位置,而不必改变组件的内部结构。

<template id="my-slot-template">
  <div>
    <slot name="content"></slot>
  </div>
</template>

<my-slot-element>
  <p slot="content">This is slotted content.</p>
</my-slot-element>

在这个例子中,<slot> 元素充当了外部内容的占位符,外部内容通过 slot 属性插入到组件的特定位置。


属性支持与监听

Web Components 支持在自定义元素上定义和使用属性,以便通过 HTML 属性或 JavaScript 动态设置和获取组件的状态。

<!-- 使用自定义元素,并设置初始属性 -->
<my-attribute-element message="Hello, Web Components!"></my-attribute-element>
<button onclick="changeMessage()">更改消息</button>
  
<script>
    class MyElement extends HTMLElement {
        static get observedAttributes() {
            return ['message'];
        }

        constructor() {
            super();
            this.render();
        }

        attributeChangedCallback(name, oldValue, newValue) {
            if (name === 'message') {
                this.render();
            }
        }

        render() {
            this.innerHTML = `<p>${this.getAttribute('message') || '默认消息'}</p>`;
        }
    }

    customElements.define('my-attribute-element', MyElement);

    function changeMessage() {
        const element = document.querySelector('my-attribute-element');
        element.setAttribute('message', '属性已更改!');
    }
</script>

👆 通过 static get observedAttributes() 方法声明了需要观察的属性 message,并在 attributeChangedCallback 中处理属性变化时的逻辑。

自定义事件

在 Web Components 中,自定义事件允许组件与外部世界进行交互。通过使用 JavaScript 的 CustomEvent 接口,可以在自定义元素中创建和派发事件,让其它部分的代码可以监听这些事件并做出响应。

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.addEventListener('click', () => {
      this.dispatchEvent(new CustomEvent('my-event', { detail: 'Hello from custom event!' }));
    });
  }
}

customElements.define('my-event-element', MyElement);

document.querySelector('my-event-element').addEventListener('my-event', (e) => {
  console.log(e.detail); // 输出: Hello from custom event!
});

在这个例子中,当用户点击自定义元素时,会派发一个自定义事件 my-event,外部代码可以监听并处理这个事件。

Web Components 组件库:Lit

Lit 是一个用于构建快速、轻量级 Web Components 的 JavaScript 库,它由 Google 的开发团队创建,旨在简化和加速开发符合 Web Components 标准的组件。Lit 本身并不是一个完整的框架,而是一个工具集,帮助开发者轻松构建、自定义和组合 Web Components。

import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';

@customElement('my-lit-element')
export class MyLitElement extends LitElement {
  static styles = css`
    p {
      color: blue;
    }
  `;

  @property({ type: String })
  message: string = 'Hello, World!';

  render() {
    return html`
      <p>${this.message}</p>
    `;
  }
}

在这个例子中,我们使用 Lit 创建了一个简单的自定义元素。Lit 提供了声明式模板、响应式状态管理和作用域样式等功能,使得开发 Web Components 变得更加简单和高效。

为什么 Web Components 没有流行?

尽管 Web Components 具有跨框架兼容性、原生支持和性能优势,但在实际开发中,Vue 和 React 等框架的组件方案仍然是主流。以下是 Web Components 未能广泛流行的几个原因:

  1. 功能局限:Web Components 的 API 相对简单,缺乏内置的状态管理、路由等高级特性。在大型应用中处理状态和复杂逻辑时,Web Components 显得力不从心。
  2. 生态系统较小:相比 Vue 和 React,Web Components 的生态系统较小,缺乏丰富的工具、库和社区支持。
  3. 开发体验:现代前端框架提供了丰富的开发工具和优化体验,而 Web Components 的开发体验相对较为原始。

Web Components 的应用场景

尽管 Web Components 在主流开发中尚未普及,但在某些场景下,它仍然具有明显的优势:

  1. 跨框架组件库:创建可在多种框架中使用的通用 UI 组件。
  2. 微前端架构:构建可独立部署、技术栈无关的应用模块。
  3. 嵌入式组件:开发可嵌入第三方网站的小部件或组件。
  4. 企业级设计系统:构建统一的、可在不同项目间共享的组件库。
  5. 长期维护的项目:利用标准化技术降低对特定框架的依赖。

结论

Web Components 提供了一种原生、标准化的方式来实现组件的封装和复用,具有跨框架兼容性和性能优势。尽管目前其在生态系统和开发体验上存在一些局限,但在特定场景下,Web Components 仍然是一个非常有价值的技术选择。随着 Web 标准的不断发展和完善,Web Components 有望在未来得到更广泛的应用。

希望本文能帮助你更好地理解 Web Components,并在实际开发中灵活运用这一技术。如果你有任何问题或建议,欢迎在评论区留言讨论!

相关文章:

  • 【图片合并PDF】多个文件夹里的图片合并PDF,一次性批量合并多个文件夹里的图片转成PDF,基于WPF完成方案分享
  • Netty启动源码NioEventLoop剖析accept剖析read剖析write剖析
  • windows系统,pycharm运行.sh文件
  • 【已解决】电脑空间告急?我的 Ollama、Docker Desktop软件卸载清理全记录
  • environment.resolvePlaceholders占位符解析优化
  • 分布式事务中XA 事务 和 两阶段提交(2PC)应该如何理解?
  • 自定义日志回调函数实现第三方库日志集成:从理论到实战
  • 前端面试:px 如何转为 rem
  • mysql select distinct 和 group by 哪个效率高
  • 单一责任原则在Java设计模式中的深度解析
  • 完全二叉树节点的数量 平衡二叉树
  • 【视频】SRS将RTMP转WebRTC、HLS流;获取RTSP转其它流
  • JavaScript 运算符详解
  • 关于stac和clac的进一步细节及EFLAGS
  • 蓝桥备赛(18)- 红黑树和 set 与 map(上)
  • 每日一题力扣2697.字典序最小回文串c++
  • (每日一题) 力扣 179 最大数
  • unittest vs pytest区别
  • Proser:新增指令批次发送功能
  • 全外显子检测家系三样本联合分析+新发变异检测分析
  • 开发平台技术创新联盟/南京百度快速排名优化
  • 武汉专业做网站开发的公司/教育培训学校
  • 非经营备案网站能贴放广告么/营销推广内容
  • 住房建设部官方网站/凌哥seo
  • 个人微信公众号怎么做微网站/深圳做推广哪家比较好
  • 沈阳定制网站开发/最近发生的热点新闻