组件化是前端发展的一个重要方向,它一方面提高开发效率,另一方面降低维护成本。主流的 Vue.js、React 及其延伸的 Ant Design、uniapp、Taro 等都是组件框架。Web Components 是一组 Web 原生 API 的总称,允许我们创建可重用的自定义组件,并在我们 Web 应用中像使用原生 HTML 标签一样使用。目前已经很多前端框架/库支持 Web Components。
本文将带大家回顾 Web Components 核心 API,并从 0 到 1 实现一个基于 Web Components API 开发的业务组件库。
最终效果:blog.pingan8787.com/exe-compone… 仓库地址:github.com/pingan8787/…
一、回顾 Web Components
在前端发展历史中,从刚开始重复业务到处复制相同代码,到 Web Components 的出现,我们使用原生 HTML 标签的自定义组件,复用组件代码,提高开发效率。通过 Web Components 创建的组件,几乎可以使用在任何前端框架中。
1. 核心 API 回顾
Web Components 由 3 个核心 API 组成:
- Custom elements(自定义元素):用来让我们定义自定义元素及其行为,对外提供组件的标签;
- Shadow DOM(影子 DOM):用来封装组件内部的结构,避免与外部冲突;
- HTML templates(HTML 模版):包括
<template>
和<slot>
元素,让我们可以定义各种组件的 HTML 模版,然后被复用到其他地方,使用过 Vue/React 等框架的同学应该会很熟悉。
另外,还有 HTML imports,但目前已废弃,所以不具体介绍,其作用是用来控制组件的依赖加载。
2. 入门示例
接下来通过下面简单示例快速了解一下如何创建一个简单 Web Components 组件。
- 使用组件
<!DOCTYPE html> <html lang="en"> <head> <script src="./index.js" defer></script> </head> <body> <h1>custom-element-start</h1> <custom-element-start></custom-element-start> </body> </html>
- 定义组件
/** * 使用 CustomElementRegistry.define() 方法用来注册一个 custom element * 参数如下: * - 元素名称,符合 DOMString 规范,名称不能是单个单词,且必须用短横线隔开 * - 元素行为,必须是一个类 * - 继承元素,可选配置,一个包含 extends 属性的配置对象,指定创建的元素继承自哪个内置元素,可以继承任何内置元素。 */ class CustomElementStart extends HTMLElement { constructor(){ super(); this.render(); } render(){ const shadow = this.attachShadow({mode: 'open'}); const text = document.createElement("span"); text.textContent = 'Hi Custom Element!'; text.style = 'color: red'; shadow.append(text); } } customElements.define('custom-element-start', CustomElementStart)
上面代码主要做 3 件事:
- 实现组件类
通过实现 CustomElementStart
类来定义组件。
- 定义组件
将组件的标签和组件类作为参数,通过 customElements.define
方法定义组件。
- 使用组件
导入组件后,跟使用普通 HTML 标签一样直接使用自定义组件 <custom-element-start></custom-element-start>
。
随后浏览器访问 index.html
可以看到下面内容:
3. 兼容性介绍
在 MDN | Web Components 章节中介绍了其兼容性情况:
- Firefox(版本63)、Chrome和Opera都默认支持Web组件。
- Safari支持许多web组件特性,但比上述浏览器少。
- Edge正在开发一个实现。
关于兼容性,可以看下图:
这个网站里面,有很多关于 Web Components 的优秀项目可以学习。
4. 小结
这节主要通过一个简单示例,简单回顾基础知识,详细可以阅读文档:
二、EXE-Components 组件库分析设计
1. 背景介绍
假设我们需要实现一个 EXE-Components 组件库,该组件库的组件分 2 大类:
- components 类型
以通用简单组件为主,如exe-avatar
头像组件、 exe-button
按钮组件等;
- modules 类型
以复杂、组合组件为主,如exe-user-avatar
用户头像组件(含用户信息)、exe-attachement-list
附件列表组件等等。
详细可以看下图:
接下来我们会基于上图进行 EXE-Components 组件库设计和开发。
2. 组件库设计
在设计组件库的时候,主要需要考虑以下几点:
- 组件命名、参数命名等规范,方便组件后续维护;
- 组件参数定义;
- 组件样式隔离;
当然,这几个是最基础需要考虑的点,随着实际业务的复杂,还需要考虑更多,比如:工程化相关、组件解耦、组件主题等等。
针对前面提到这 3 点,这边约定几个命名规范:
- 组件名称以
exe-功能名称
进行命名,如exe-avatar
表示头像组件; - 属性参数名称以
e-参数名称
进行命名,如e-src
表示src
地址属性; - 事件参数名称以
on-事件类型
进行命名,如on-click
表示点击事件;
3. 组件库组件设计
这边我们主要设计 exe-avatar
、exe-button
和 exe-user-avatar
三个组件,前两个为简单组件,后一个为复杂组件,其内部使用了前两个组件进行组合。这边先定义这三个组件支持的属性:
这边属性命名看着会比较复杂,大家可以按照自己和团队的习惯进行命名。
这样我们思路就清晰很多,实现对应组件即可。
三、EXE-Components 组件库准备工作
本文示例最终将对实现的组件进行组合使用,实现下面「用户列表」效果:
1. 统一开发规范
首先我们先统一开发规范,包括:
- 目录规范
- 定义组件规范
- 组件开发模版
组件开发模版分 index.js
组件入口文件和 template.js
组件 HTML 模版文件:
// index.js 模版 const defaultConfig = { // 组件默认配置 } const Selector = "exe-avatar"; // 组件标签名 export default class EXEAvatar extends HTMLElement { shadowRoot = null; config = defaultConfig; constructor(){ super(); this.render(); // 统一处理组件初始化逻辑 } render() { this.shadowRoot = this.attachShadow({mode: 'closed'}); this.shadowRoot.innerHTML = renderTemplate(this.config); } } // 定义组件 if (!customElements.get(Selector)) { customElements.define(Selector, EXEAvatar) }
// template.js 模版 export default config => { // 统一读取配置 const { avatarWidth, avatarRadius, avatarSrc } = config; return ` <style> /* CSS 内容 */ </style> <div class="exe-avatar"> /* HTML 内容 */ </div> ` }
2. 开发环境搭建和工程化处理
为了方便使用 EXE-Components 组件库,更接近实际组件库的使用,我们需要将组件库打包成一个 UMD 类型的 js 文件。这边我们使用 rollup 进行构建,最终打包成 exe-components.js
的文件,使用方式如下:
<script src="./exe-components.js"></script>
接下来通过 npm init -y
生成 package.json
文件,然后全局安装 rollup 和 http-server(用来启动本地服务器,方便调试):
npm init -y npm install --global rollup http-server
然后在 package.json
的 script
下添加 "dev"
和 "build"
脚本:
{ // ... "scripts": { "dev": "http-server -c-1 -p 1400", "build": "rollup index.js --file exe-components.js --format iife" }, }
其中:
"dev"
命令:通过 http-server 启动静态服务器,作为开发环境使用。添加-c-1
参数用来禁用缓存,避免刷新页面还会有缓存,详细可以看 http-server 文档;"build"
命令:将 index.js 作为 rollup 打包的入口文件,输出exe-components.js
文件,并且是 iife 类型的文件。
这样就完成简单的本地开发和组件库构建的工程化配置,接下来就可以进行开发了。