前言
你是否曾经需要一组可重用的UI组件,可以直接将它们加入应用程序,并获得惊人的体验?
你是否曾经创建自己的组件并在公司里分享,包括那些使用不同前端框架的团队?
你是否曾经要实现品牌体验或设计语言,比如微软的Fluent UI或谷歌的Material Design?
你是否曾经想要缩短应用程序的启动时间、提升渲染速度或减少内存消耗?
你是否想过基于原生Web组件构建应用程序,而不受现代JavaScript前端环境的影响?
你是否为项目中多团队高频迭代开发核心组件的质量和稳定性后知后觉而焦虑?
背景
智能运营团队在活动可视化配置项目中,创建活动300+,上线的半年时间内访问量1800W+,每天的PV突破10W+ ,每天登陆用户数4W+。可视化配置工具细分的业务场景也越来越多,有H5可视化、APP可视化、小程序可视化等多类业务,覆盖到**各端。
我们平台运营团队负责运营活动、APP配置等任务,提供平台能力支持业务线发展,智能运营平台本身是一个平台业务,还承担着新业务发展平台的角色,这意味着想要支持好平台业务的发展是一件非常有挑战的事情。
发展路线
以APP可视化为例,在数字化转型初期,APP页面可视化配置对接了两个APP的诉求和使用 场景有很大的不同,经过多个版本的迭代APP可视化配置存在着业务互斥和优先级的关系。业务方急需在短期内快速更新迭代来验证业务方向。因此APP开发复杂度和协作成本是极高的,同时带来迭代边际效应越来越低。由于不同团队/人员缺少统一、标准的开发规范和风险意识,体验和稳定性难以保障,严重影响用户增长和业务发展。
面临的问题
- 组件在多个业务上无法复用。
- 类似的组件在各端展示风格上不统一,差异较大。
- 开发复杂度高,公共能力提取困难。
- 团队间无法协同工作。
解决问题的思路
我们公司甚至其他公司各个团队搭建都分别使用着不同的框架及对应的组件库产品,以满足各自的业务诉求,提升研发效能。这些组件库体系各自独立维护,彼此割裂。随着各业务的规模不断扩大,这种割裂局面愈发严重。
而且当不同小组的业务需要合并、复用,或者人员变动的时候,切换到新的框架,带来的成本非常高昂。其中有一部分原因是当框架切换的时候,UI库也必须同步切换。
目前看到的组件库没有特别全面的,大都是按照ant design、 element ui,这种细较粒度的拆分来做的,正是因为拆分太细,导致几乎都不带什么逻辑,所以大都是带样式的元素这种形态,其实和使用CSS样式库没什么区别,反而会因为需要先运行js注册组件而导致页面渲染阻塞。
在这个过程中,我们结合组件化思想,以及 Web Components 规范辅助理解。Web Components 规范是 W3C 推出的一套用于封装具有复用性、互动性前端组件的技术规范,旨在提供一种标准的组件化模式。
目前流行的几个前端框架( Vue / React / Angular )都在一定程度上遵循了这套规范,自定义组件使用的是 Shadow DOM,这项技术是 Web Components 规范的一部分。
整体数据
下图显示了customElements.define至少调用一次的页面加载百分比(在 Chrom e 中)
我们可以看到,在 Chrome 浏览器中查看的所有网站中,超过 15% 的网站至少注册了一个自定义元素。相比之下,根据 w3techs.com 的数据,只有 2.3% 的网站使用 React。
那么,使用 Web Components 带来了哪些改变?我们来举个。
以前:
A:我造了一个Vue3的轮子,是关于身份信息卡片的。
B:正好,我需要我试试。wtf 是vue3的啊,我vue2用不了。
C:好可惜,我是react框架,也用不了。
D:啊!我是angular,也用不了!
E:老jq项目能不能用?
F:我引入了,但是和我目前项目的逻辑冲突了!
假如基于 Web Components 构建:
A:我造了一个Web Components的轮子,是关于身份信息卡片的,引入标签就能用了。
B:正好,我需要我试试,好像可以,用起来好简单!
C:我是react框架,没问题。
D:啊!我是angular,没问题,也完美!
E:老jq项目能不能用?能啊 nice!
F:我引入了,完美,无冲突。
「前端 组件化」 一直是前端工程师绕不开的话题,不管是日常业务开发封装业务组件;还是基础设施建设开发UI组件或基础组件;亦或者是 React 、Vue、Angular 等MV*框架,都是秉承着组件作为基础原子概念,所以可以肯定的是: 组件是下一代 web 技术的发展关键 。
每个组件就类似原子,经过不断的组合、复用,最终组成我们看到的页面。
我们可以把“前端 组件化”理解为“面向对象编程思想在前端 UI 领域的具体实践,将部分 UI 内容抽离为独立的可复用的组件”,这样做有这样 3 点优势:
- 提高代码可复用性
这是组件化最直接的优点,如果不用组件,每次遇到相同的业务场景都需要重新编写代码,而被抽离后的组件可以在其适用的场景内被重复使用,很大程度上降低了开发耗时。
- 降低代码维护难度
想象一下,假如一个页面的所有 UI 源码都集中在同一个 HTML 文件中,当页面中的导航栏出现 Bug,你需要在上千行甚至上万行的 HTML 文件中找到导航栏对应的 HTML 标签。如果将导航栏抽离为一个组件,那么你仅仅需要在这个组件内寻找。这类案例在工作中普遍存在,通过这个例子可以充分说明组件化在降低代码维护难度方面的优势。
- 降低系统重构难度
《重构:改善既有代码的设计》讲了:重构并不是当系统复杂度提升到一定程度难以维护时的一次性行为,而是一种高频的、小规模的日常行为。直白点儿说就是:应该不断通过重构来改善系统,不管重构的范围有多小。但是这种实践方式对于代码的可维护性有很大的挑战,这也间接说明了组件化对重构工作的正面影响:通过提高代码的可维护性,间接降低了系统的重构难度。
基于现状和我们对未来的规划、前端发展趋势,我们基于 Web Components 开发一套UI框架,做到一次开发到处使用的最终目的:
- 不依赖任何框架,在任何生态中都能运行良好,达到高效复用,例如在 同时在React,Angular,Vue 中使用他们
- 可以在任何一种框架中使用,不用加载任何模块、代码量小
- 易于继承,不需要编译
- 真正的局部CSS作用域
- 在主流浏览器上的支持已经极其良好
Web Components介绍
Web Components是一套不同的技术,可以通过它创建可重用的自定义元素,并在Web应用中使用这些元素。Web Components是一个浏览器原生支持的组件化方案,允许创建新的自定义、可封装的HTML标签,在使用时,不需要加载任何额外的模块,不需要依赖任何第三方框架,可以跨现代浏览器工作,并可与任何支持 HTML 的 JavaScript 库或框架一起使用。
Web Components 特性简单的总结:
- 「标准统一」浏览器支持的底层api。
- 「灵活多变」可用于封装基础、业务组件。
- 「无侵入」以标签隔离、不会侵入业务代码、组件间不会相互影响。
- 「清晰」简单直接、不依赖外部模块、代码体积小。
web components 组件化由 3 部分组成。
- Custom elements(自定义元素) :一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
- Shadow DOM(影子DOM) :一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
- HTML templates(HTML模板) :
<template>和<slot>元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
Web components 的重要功能是封装——可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,使代码更加干净、整洁。Shadow DOM 接口是关键所在,它可以将一个隐藏的、独立的 DOM 附加到一个元素上。
附加到哪个元素上,和定义 custom element 时有关,如果是独立元素,附加到 document body 上;如果是继承元素,则附加到继承元素上。
Shadow host:Shadow DOM 附加到的常规 DOM 节点。
Shadow tree:Shadow DOM 内部的 DOM 树。
Shadow boundary:Shadow DOM 结束的地方,普通 DOM 开始的地方。
Shadow root:Shadow tree的根节点。
Web Component示例
定义一个 Web Component,例如:
<template id="myCustomElementTemplate">
<div class="myDiv">
<div class="devText">Here is some amazing text</div>
</div>
</template>
<script type="module">
let importDoc = import.meta.document
class myCustomElement extends HTMLElement {
constructor() {
super()
let shadowRoot = this.attachShadow({ mode: 'open' })
let template = importDoc.getElementById('myCustomElementTemplate')
shadowRoot.appendChild(template.content.clone(true))
}
}
customElements.define('myCustomElement', myCustomElement)
</script>
由于直接作为 HTML 文件使用,因此不需要再通过字符串字面量的方式来内嵌模版内容,或者通过复杂的预处理步骤进行嵌入。
HTML Modules 提案极大地降低了对构建工具的需求,开发人员可以能够便捷高效地发布源代码,提升开发体验。
HI-kits UI组件库介绍
官网地址:<点击这里>
hi-element 是一组JavaScript包。hi-element 包含实现 Web Components API的核心类。
hi-element 包是一个包含Web组件类、模板和其他辅助程序的库,设计系统(例如 Fluent Design或 Material Design)打算将它们组合到注册Web组件中。这个包不导出 自定义元素,而是实现无样式的语义和可访问的标记和行为,这些标记和行为可以进一步组合成样式化的自定义元素。因此,开发人员可以通过应用CSS样式和重用内置行为来实现自定义设计语言。
开发模式
hi-kits 是在 hi-element 规范基础上封装的跨框架UI组件库,将文件导出了一个 Web 组件库,使用符合 hi-kits 设计语言的样式表组成了 hi-kits 组件库,我们在使用上比较接近流行框架的开发模式。可以优雅的在原生、vue、react、angular等框架中使用。
hi-kits在构建统一/多端覆盖/跨技术栈的前端应用时更具优势。
特性
- 跨框架。无论是react、vue、angular 还是原生项目均可使用。
- 组件化。shadow dom真正意义上实现了样式和功能的组件化。
- 类原生。一个组件就像使用一个div标签一样。
- 无依赖。纯原生,无需任何预处理器编译。
- 无障碍。支持键盘访问。
- 跨终端。组件库同时满足桌面端和移动端的需求。
- 高效。没有庞大的运行时,加载速度快
真正的实现一次编写,到处运行。
hi-kits 组件实例:
// 核心库
import { customElement, attr, css, html, ref, observable, HIElement } from 'hi-element';
// 样式助手
import { Background, Style } from '@utils/style';
// 样式
const styles = css`
:host{
color: #fff;
}
.Background{
position:absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-size: 100%;
z-index: -1;
}
`;
// 模版文件
const template = html<HiComponent>`
<div class="Background" ${ref('Background')}></div>
<slot></slot>
`;
// 定义元素
@customElement({
name: 'my-component',
styles,
template,
})
export class HiComponent extends HIElement {
// ------------------ 构造函数 ------------------
constructor() {
super();
}
// ------------------ 参数 ------------------
@observable
Background: HTMLDivElement;
// ------------------ 属性 ------------------
@attr background: string;
backgroundChanged(oldValue, newValue): void {
if (newValue) {
Style(this.Background)(Background(newValue));
}
}
// ------------------ 自定义函数 ------------------
/**
* 当自定义元素第一次被连接到文档DOM时被调用
* @internal
*/
connectedCallback(): void {
super.connectedCallback();
}
}
运行的结果:
hi-kits UI库在开发中,大大减少了代码的冗余,使代码看起来更加简洁、清晰。
目前我们的UI库已经有100+个组件,包含基础组件库、业务组件库、自定义组件库,可以根据需求进行定制的组件开发。
接来下 Hi-kits将按照如下规划,快速迭代完善:
- 持续丰富组件种类
- 组件动效的持续优化
- 持续优化 基于 GitHub 的 CI/CD 基础设施建设
- 提供在线色彩配置能力
- 组件库的国际化支持能力
- 我们最大的特色是可以定制的业务组件,提高开发效率。
需要明确一点,那就是 Web Components 作为一套规范并不试图取代框架的职责,而只是提供一套和现有 DOM API 风格一致的封装机制。每个 component 内部可以采用不同的实现,但是不管你内部用什么框架,对外暴露的都是一样的 API。所以你可以在框架 A 写的应用里很轻松地使用内部由框架 B 实现的组件。这样组件复用的一大难题:框架不兼容的问题就被抹平了。这才是 Web Components 的核心意义所在。
它确实在该领域有着不可比拟的优势,尤其是对重用度很高的基于 Web的组件应用来说,使用 Web Components 是再好不过的选择了。
使用方式:
React中按需引用Hi-kits
在项目中引入hi-kits
import { HI_CONFIG } from 'hi-kits/_shared/config/config';
import { HiBoard } from "hi-kits";
import { HiButton } from "hi-kits";
页面初始化时加载组件
HI_CONFIG.case.install([HiBoard, HiButton])
页面上使用
<h-button type="primary">Primary Button</h-button>
Vue中按需引用Hi-kits
import { HI_CONFIG } from 'hi-kits/_shared/config/config';
import { HiBoard } from "hi-kits";
import { HiButton } from "hi-kits";
HI_CONFIG.case.install([HiBoard, HiButton])
<h-button type="primary">Primary Button</h-button>
Angular中按需引用配置文件
/**
* HI_KITS 公共组件定义
* @class HIKITSCOMMONMODULES
* @description
* 将hi-kits 组件引入在项目中作为公共组件导入导出
*/
import { NgModule } from "@angular/core";
/*----------------- hikits库 ---------------------*/
import { HI_CONFIG } from 'hi-kits/_shared/config/config';
import { HiBoard } from "hi-kits";
import { HiButton } from "hi-kits";
HI_CONFIG.case.install([HiBoard, HiButton])
@NgModule({})
export class HIKITSCOMMONMODULES {}
<h-button type="primary">Primary Button</h-button>
可以看到hi-kits在各个流行框架中的引用方式、组件的使用方式是统一的。方便开发人员切换框架时不会出现差异化,降低了学习成本,提高开发效率。
写在最后
随着个别老旧浏览器的淘汰,并且现代浏览器具备快速更新的能力,Web Components 作为 Web 的原生特性,真正具备着达到 0KB Runtime 的能力。未来的 Web App 必将更加偏向于 Evergreen Browser,从而充分发挥 Web 功能。
某自称 0KB Runtime 的框架仅仅是把成本分担到每个组件中,应用达到一定规模后的总体成本比 Shared Runtime 反而更高
国外对于新技术的应用和探索一直领先我们,希望国内的小伙伴们也快速认识到趋势,并可以把前端的新思想,新趋势带入到业务中,提升研发效率,得到正向的反馈。同时也多多贡献自己的想法和轮子,提升中国前端在世界的影响力。