基于 WebComponents 封装 UI 组件库
如何基于 WebComponents 封装 UI 组件库
https://www.zoo.team/article/web-components
前言
作为一名前端攻城狮,相信大家也都在关注着前端的一些新技术,近些年来前端组件化开发已为常态,我们经常把重用性高的模块抽离成一个个的组件,来达到复用的目的,这样减少了我们的维护成本,提高了开发的效率。但是都有一个缺点离不开框架本身,因为我们浏览器本身解析不了那些组件。那么有没有一种技术也可以达到这种效果呢?答案就是今天的主角 Web Components。
“Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的 web 应用中使用它们。
目前 W3C 也在积极推动,并且浏览器的支持情况还不错。FireFox、Chrome、Opera 已全部支持,Safari 也大部分支持,Edge 也换成 webkit 内核了,离全面支持应该也不远了。当然社区也有兼容的解决方案 webcomponents/polyfills。
WebComponents 三要素和生命周期
Button 组件示例
首先我们就从一个最简单的 Button 组件开始,我们可以通过在组件中传入 type 来改变按钮的样式,并且动态监听了数据的变化。
// html
<cai-button type="primary">
<span slot="btnText">
按钮
span>
cai-button>
<template id="caiBtn">
<style>
.cai-button {
display: inline-block;
padding: 4px 20px;
font-size: 14px;
line-height: 1.5715;
font-weight: 400;
border: 1px solid #1890ff;
border-radius: 2px;
background-color: #1890ff;
color: #fff;
box-shadow: 0 2px #00000004;
}
.cai-button-warning {
border: 1px solid #faad14;
background-color: #faad14;
}
.cai-button-danger {
border: 1px solid #ff4d4f;
background-color: #ff4d4f;
}
style>
<div class="cai-button"> <slot name="btnText">slot> div>
template>
<script>
const template = document.getElementById("caiBtn");
class CaiButton extends HTMLElement {
constructor() {
super()
this._type = {
primary: 'cai-button',
warning: 'cai-button-warning',
danger: 'cai-button-danger',
}
// 开启 shadow dom
const shadow = this.attachShadow({
mode: 'open'
})
const type = this
const content = template.content.cloneNode(true) // 克隆一份 防止重复使用 污染
// 把响应式数据挂到 this
this._btn = content.querySelector('.cai-button')
this._btn.className += ` ${this._type[type]}`
shadow.appendChild(content)
}
static get observedAttributes() {
return ['type']
}
attributeChangedCallback(name, oldValue, newValue) {
this[name] = newValue;
this.render();
}
render() {
this._btn.className = `cai-button ${this._type[this.type]}`
}
}
// 挂载到 window
window.customElements.define('cai-button', CaiButton)
script>
三要素、生命周期和示例的解析
Custom elements(自定义元素): 一组 JavaScript API,允许您定义 custom elements 及其行为,然后可以在您的用户界面中按照需要使用它们。在上面例子中就指的是我们的自定义组件,我们通过
class CaiButton extends HTMLElement {}
定义我们的组件,通过window.customElements.define('cai-button', CaiButton)
挂载我们的已定义组件。Shadow DOM(影子 DOM ):一组 JavaScript API,用于将封装的“影子” DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。使用
const shadow = this.attachShadow({mode : 'open'})
在 WebComponents 中开启。HTML templates(HTML 模板)slot :template 可以简化生成 dom 元素的操作,我们不再需要 createElement 每一个节点。slot 则和 Vue 里面的 slot 类似,只是使用名称不太一样。
内部生命周期函数
connectedCallback
: 当 WebComponents 第一次被挂在到 dom 上是触发的钩子,并且只会触发一次。类似 Vue 中的 mounted React 中的 useEffect(() => {}, []),componentDidMount。disconnectedCallback
: 当自定义元素与文档 DOM 断开连接时被调用。adoptedCallback
: 当自定义元素被移动到新文档时被调用。attributeChangedCallback
: 当自定义元素的被监听属性变化时被调用。上述例子中我们监听了 type 的变化,使 Button 组件呈现不同状态。
虽然 WebComponents 有三个要素,但却不是缺一不可的,WebComponents 借助 shadow dom 来实现样式隔离,借助 templates 来简化标签的操作。
在这个例子用我们使用了 slot 传入了俩个标签之间的内容,如果我们想要不使用 slot 传入标签之间的内容怎么办?
我们可以通过 innerHTML 拿到自定义组件之间的内容,然后把这段内容插入到对应节点即可。
组件通信
了解上面这些基本的概念后,我们就可以开发一些简单的组件了,但是如果我们想传入一些复杂的数据类型(对象,数组等)怎么办?我们只传入字符串还可以么?答案是肯定的!
传入复杂数据类型
使用我们上面的 Button,我们不仅要改变状态,而且要想要传入一些配置,我们可以通过传入一个 JSON 字符串
// html
"btn">
</cai-button>