如何快速编写一个 Web Components 组件?

前言

Web Components 是一种用于构建可复用的定制元素的技术,它允许开发者创建自定义的 HTML 标签,提供了一种在不同框架和库中共享组件的方式。本文将介绍 Web Components 的基本概念、编写和生命周期方法,以及在普通使用和 Vue 框架中的应用方法。

一、什么是 Web Components?

Web Components 是一组浏览器标准,它由四个主要技术组成:

  • Custom Elements:允许开发者定义自己的 HTML 元素。
  • Shadow DOM:提供了一种将样式和脚本封装起来,不被外部影响的方法。
  • HTML Templates:允许开发者定义片段的模板,以便稍后在不同位置进行复制和粘贴。

二、编写 Web Components

1. 定义一个简单的 Web Component

javascript 复制代码
class MyCustomElement extends HTMLElement {
  constructor() {
    super()
    // 添加影子DOM
    const shadow = this.attachShadow({ mode: 'open' })

    // 创建一个span元素
    const span = document.createElement('span')
    span.textContent = 'Hello, World!'

    // 将span元素添加到影子DOM中
    shadow.appendChild(span)
  }
}

customElements.define('my-custom-element', MyCustomElement)

2. 生命周期

生命周期如下:

  • connectedCallback : 当 custom element 自定义标签首次被插入文档 DOM 时,被调用,类似于 Vue 中的 mounted 周期函数
  • disconnectedCallback :当 custom element 从文档 DOM 中删除时,被调用,类似于 Vue 中的 destroyed 周期函数
  • attributeChangedCallback : 当静态属性 observedAttributes 中定义的属性被添加、修改、移除或替换时被调用
javascript 复制代码
class MyCustomElement extends HTMLElement {
  constructor() {
    super()
  }

  connectedCallback() {
    console.log('Element connected to the DOM')
  }

  disconnectedCallback() {
    console.log('Element removed from the DOM')
  }

  adoptedCallback() {
    console.log('Element moved to a new document')
  }

  attributeChangedCallback(name, oldValue, newValue) {
    console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`)
  }
}

customElements.define('my-custom-element', MyCustomElement)

3. 自定义事件

在 Web Components 内部,可以通过自定义事件来进行与外部的通信。要在 Web Components 内部暴露自定义事件,可以使用 CustomEvent 类来创建并触发自定义事件。

javascript 复制代码
class MyCustomElement extends HTMLElement {
  constructor() {
    super()
    // ...其他初始化操作
  }

  // 定义一个自定义事件暴露方法
  exposeCustomEvent() {
    // 创建一个自定义事件,可以传递一些数据
    const event = new CustomEvent('customEventName', {
      detail: { message: 'Hello from custom event!' },
      bubbles: true, // 是否冒泡
      composed: true, // 是否能穿越 Shadow DOM
    })

    // 触发自定义事件
    this.dispatchEvent(event)
  }
}

customElements.define('my-custom-element', MyCustomElement)

在外部代码中,可以通过以下方式监听和响应这个自定义事件:

javascript 复制代码
const myCustomElement = document.querySelector('my-custom-element')

myCustomElement.addEventListener('customEventName', (event) => {
  console.log('Received custom event:', event.detail.message)
})

通过暴露自定义事件,Web Components 可以与外部环境进行有效的通信,从而实现更加灵活和可复用的组件。

4. 数据绑定

需要监听的属性需要放到静态属性 observedAttributes 中,这个属性是个数组,当 observedAttributes 属性中的属性发生变化时,会触发 attributeChangedCallback() 回调,回调参数���含属性名 name、变化前的值 oldValue 和变化后的值 newValue

js 复制代码
// 为这个元素创建类
class MyCustomElement extends HTMLElement {
  static observedAttributes = ['color', 'size']

  constructor() {
    super()
  }

  attributeChangedCallback(name, oldValue, newValue) {
    console.log(`属性 ${name} 已由 ${oldValue} 变更为 ${newValue}。`)
    this[name] = newValue
    this.updateDiv()
  }

  updateDiv() {
    const p = this.shadowRoot.querySelector('p')
    p.textContent = `颜色:${this.color},尺寸:${this.size}`
  }
}

customElements.define('my-custom-element', MyCustomElement)

业务方使用方法如下:

html 复制代码
<my-custom-element id="customElement" size="100" color="red">4214</my-custom-element>
<button class="change-text">修改文案</button>

<script>
  const customElement = document.querySelector('#customElement')

  const changeBtn = document.querySelector('.change-text')
  changeBtn.onclick = function () {
    customElement.setAttribute('color', 'yellow')
    customElement.setAttribute('size', '300')
  }
</script>

5. 编写样式

有两种方法为 shadow DOM 添加样式:

  1. 编程式,通过构建一个 CSSStyleSheet 对象并将其附加到影子根
  1. 创建一个空的 CSSStyleSheet 对象
  2. 使用 CSSStyleSheet.replace()CSSStyleSheet.replaceSync() 设置其内容
  3. 通过将其赋给 ShadowRoot.adoptedStyleSheets 来添加到影子根
html 复制代码
<div id="host"></div>
<span>I'm not in the shadow DOM</span>
<script>
  const sheet = new CSSStyleSheet()
  sheet.replaceSync('span { color: red; border: 2px dotted black;}')

  const host = document.querySelector('#host')

  const shadow = host.attachShadow({ mode: 'open' })
  shadow.adoptedStyleSheets = [sheet]

  const span = document.createElement('span')
  span.textContent = "I'm in the shadow DOM"
  shadow.appendChild(span)
</script>
  1. 声明式,通过在一个 <template> 元素的声明中添加一个 <style> 元素。
html 复制代码
<template id="my-element">
  <style>
    span {
      color: red;
      border: 2px dotted black;
    }
  </style>
  <span>I'm in the shadow DOM</span>
</template>

<div id="host"></div>
<span>I'm not in the shadow DOM</span>

<script>
  const host = document.querySelector('#host')
  const shadow = host.attachShadow({ mode: 'open' })
  const template = document.getElementById('my-element')

  shadow.appendChild(template.content)
</script>

三、使用 Vue 构建 Web Component

Vue 提供了 defineCustomElement 方法来创建 Web Component,接收参数和 defineComponent 一样。

js 复制代码
import { defineCustomElement } from 'vue'

const MyVueElement = defineCustomElement({
  // 这里是同平常一样的 Vue 组件选项
  props: {},
  emits: {},
  template: `...`,

  // defineCustomElement 特有的:注入进 shadow root 的 CSS
  styles: [`/* inlined css */`],
})

// 注册自定义元素
// 注册之后,所有此页面中的 `<my-vue-element>` 标签
// 都会被升级
customElements.define('my-vue-element', MyVueElement)

// 你也可以编程式地实例化元素:
// (必须在注册之后)
document.body.appendChild(
  new MyVueElement({
    // 初始化 props(可选)
  })
)

组件中的 emit 触发的事件参数存放在 CustomEvent 对象的 detail 属性中,该属性是个数组

defineCustomElement 也可以搭配 SFC 使用。一个以自定义元素模式加载的 SFC 将会内联其 <style> 标签为 CSS 字符串,并将其暴露为组件的 styles 选项。这会被 defineCustomElement 提取使用,并在初始化时注入到元素的 shadow root 上。

开启该模式需要将组件文件以 .ce.vue 结尾

总结

单纯的 Web Components 的编写可以做到框架无关,但是这又会失去语言框架带来的便利性,Vue 提供的 defineCustomElement 方法能让我们在编写正常组件的同时生成框架无关的组件,这会轻松很多。

参考资料

developer.mozilla.org/zh-CN/docs/...

相关推荐
也无晴也无风雨42 分钟前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui