一、什么是 Web Components?
Web Components 是一个浏览器的新功能,它允许开发者创建可重用的定制元素,该定制元素的功能封装在代码之外,定制元素的功能部分由自身的 html 结构、css 样式以及 javascript 代码组成,并且不会干扰到代码中其他元素,该元素被定义之后可以在任何 web 应用中使用。它不依赖于任何框架,可实现跨框架使用,这也解决了项目重构时换框架即需要重新开发组件的痛点。
浏览器兼容性可参考:Web Components 的浏览器兼容性
二、为什么使用 Web Components?
相比于其他组件,使用 Web Components 技术开发的组件拥有以下几个显著优点:
- 一劳永逸:组件拥有更长的寿命,它不需要适应新的技术而重写。虽然我们团队目前的主要技术栈是 vue,但是也要考虑到将来项目重构时会面临技术栈升级或者更换的一个情况。
- 语法简单易学:组件仅由 html、css、javascript 三部分组成,使用它可以不像使用依赖库或者框架的组件一样去额外学习一些框架的特定语言。
- 可移植性强:组件可以在任何web应用中使用,因为很少甚至没有依赖,组件的使用障碍要明显低于依赖库或者框架的组件。
但同时,Web Components 还存在一些弊端,由于目前浏览器支持性还不太高,而且官方教程写的也不是非常清晰易懂,使用起来还是有一定难度的。
由于我们团队开发的项目的只需要兼容 google 浏览器,而且团队技术栈又比较广泛,涉及 vue、react、angluar,考虑到业务组件的可移植性,我选择使用 Web components。
三、怎么创建及使用 Web Components?
使用部分的介绍以我要做的业务组件为例,该组件主要实现两个功能:
- 在浏览器展示一段文字;
- 所展示的这段文字中的关键字要标红。
接下来让我们在实践中看一下如何构建及使用 web component。
3.1 使用 customElements.define() 创建一个自定义元素
window.customElements
是 CustomElementRegistry
的实例,CustomElementRegistry
提供注册自定义元素和查询已注册元素的方法。
customElements.define()
用于定义自定义 HTML 元素,它接受三个参数:
- 第一个参数为所创建元素的名称 (注:为了和原生的元素区分开,元素的名称不能是单个单词,且其中必须要有短横线,eg: emphasize-words);
- 第二个参数为定义元素行为的类;
- 第三个参数为可选参数,是一个包含extends属性的配置对象,它指定所创建的元素继承自哪个内置元素,可以继承任何内置元素。
// emphasize-words.js
(function() {
const target_template = document.createElement("template");
target_template.setAttribute("id", "emphasizeWordsTemplate");
//引号内填写的 HTML 代码
target_template.innerHTML = `
<style>
</style>
<span id="words"></span>
`;
document.body.append(target_template);
// 将父组件传来的纯文本变成带样式(将关键词标红)的 html
class EmphasizeWords extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow( { mode: 'closed' } );
var templateElem = document.getElementById('emphasizeWordsTemplate');
var content = templateElem.content.cloneNode(true);
// 文本
let text = this.getAttribute('text');
// 关键词
let keyWords = String(this.getAttribute('keyWords')).split(',');
// 标记颜色
let color = this.getAttribute('markColor');
// 将文本中关键词替换
for (let i = 0; i < keyWords.length; i++) {
let r = new RegExp(keyWords[i], "ig");
if(r.test(text)) {
text = text.replace(r, `<font color="${color}">${keyWords[i]}</font>`)
}
}
content.getElementById('words').innerHTML = text
shadow.appendChild(content);
}
}
window.customElements.define('emphasize-words', EmphasizeWords);
})();
复制代码
3.2 引入自定义元素
Web components 有两种引入方式(vue 中):
- 在 vue 的主渲染文件 index.html 使用
script
标签将刚刚创建的 Web components 的 js 文件引入:
<script src="./emphasize-words" defer></script>
复制代码
- 在 vue 的入口文件 main.js 或某个独立 vue 组件中使用 ES module 的形式引入:
import './emphasize-words';
复制代码
3.3 使用 Web component
web component 通过向浏览器注册自定义元素实现,注册后的自定义元素,使用方法上和原生的标签没有什么太大的区别,你只需要传入自定义元素要求的属性即可,例如:
<emphasize-words text="关键词标红" keyWords="关键词" markColor="red"></emphasize-words>
复制代码
在 vue、react 之类的框架中使用则需要遵守各自的语法,例如在 vue 中你可以这样使用它:
<template>
<div>
<emphasize-words :text="text" :keyWords="keyWords" markColor="red"></emphasize-words>
</div>
</template>
<script>
export default {
data() {
return {
text: '文案本意是指放书的桌子,后来指在桌子上写字的人。现在指的是公司或企业中从事文字工作的职位,就是以文字来表现已经制定的创意策略。文案是一个与广告创意先后相继呈现的表现过程、发展过程与深化过程, 多存在于广告公司,企业宣传与新闻策划工作等。',
keyWords: ['文案', '新闻策划', '广告'],
}
},
}
</script>
复制代码
3.4 Web component 的声明周期
如上 3.1 的示例可能会产生一个问题 —— 若这个组件还未加载完就被调用,会导致组件报错。这时,就要用到生命周期函数。
在 custom element 的构造函数中,可以指定多个不同的回调函数,它们将会在元素的不同生命时期被调用:
- connectedCallback:当 custom element 首次被插入文档DOM时,被调用。
- disconnectedCallback:当 custom element 从文档DOM中删除时,被调用。
- adoptedCallback:当 custom element 被移动到新的文档时,被调用。
- attributeChangedCallback: 当 custom element 增加、删除、修改自身属性时,被调用。
将上述功能代码的调用位置稍作修改即可(等组件加载完毕再执行功能代码):
let target_template = document.createElement("template");
target_template.setAttribute("id", "emphasizeWordsTemplate");
//引号内填写的 HTML 代码
target_template.innerHTML = `
<style>
</style>
<span id="words"></span>
`;
document.body.append(target_template);
class EmphasizeWords extends HTMLElement {
constructor() {
super();
}
init(){
let shadow = this.attachShadow( { mode: 'open' } );
let templateElem = document.getElementById('emphasizeWordsTemplate');
let content = templateElem.content.cloneNode(true);
// 将父组件传来的文本变成带样式的的 html
console.log(this, this.getAttribute('text'));
// 文本
let text = this.getAttribute('text');
console.log(text);
// 关键词
let keyWords = String(this.getAttribute('keyWords')).split(',');
// 标记颜色
let color = this.getAttribute('markColor');
// 将文本中关键词替换
for (let i = 0; i < keyWords.length; i++) {
let r = new RegExp(keyWords[i], "ig");
if(r.test(text)) {
text = text?.replace(r, `<font color="${color}">${keyWords[i]}</font>`)
}
}
content.getElementById('words').innerHTML = text
shadow.appendChild(content);
}
connectedCallback() {
console.log('被加载')
this.init();
}
disconnectedCallback() {
console.log('被删除')
}
adoptedCallback() {
console.log('被移动到新的文档')
}
attributeChangedCallback() {
console.log('增加、删除、修改自身属性')
}
}
window.customElements.define('emphasize-words', EmphasizeWords);
复制代码
四、展示结果
浏览器展示结果: