引言
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 应用程序。