之前就有在上一次更文挑战中看到掘金博主介绍过 Web Components,而最近也是有一篇文章介绍已经有一个组件库 Quark Design 是基于 Web Components 实现,并且已经在生产环境中验证过了,所以这就有必要研究一下了
简介
作为开发者,我们都知道尽可能多的重用代码是一个好主意。这对于自定义标记结构来说通常不是那么容易 — 想想复杂的 HTML(以及相关的样式和脚本),有时您不得不写代码来呈现自定义 UI 控件,并且如果您不小心的话,多次使用它们会使您的页面变得一团糟。Web Components 旨在解决这些问题 — 它由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。- MDN
Web Components 的解决方案为下面三个
HTML templates(HTML模板):<template>和<slot>元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。Custom elements(自定义元素):一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。Shadow DOM(影子DOM):一组JavaScript API,用于将封装的“影子”DOM 树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
很快啊,我就发现它和我经常使用的 Vue 有些许联系
HTML templates
HTML templates 纯纯 Vue 的模板语法,而且还可以重用,注意,无论是 Vue 的模板语法 还是 React 的 JSX 都是需要定义一个完整的组件,才可以重用,对于有些并不是强交互逻辑的而是偏展示的内容,这样会有点难受

那 HTML templates 所谓的重用怎么重用呢?
<!-- template -->
<template id="my-paragraph">
<p>My paragraph</p>
</template>
// custom component
let template = document.getElementById('my-paragraph');
let templateContent = template.content;
document.body.appendChild(templateContent.cloneNode(true));
通常会使用 Node.cloneNode() 的方法,我猜测可能是存在模板的 DOM 单一实例可能会导致事件多次触发只响应一次的问题,但这里先留个猜想
Custom elements
Custom elements 其实就是组件的逻辑部分,它拥有四个生命周期函数(初显组件锋芒)
connectedCallback:当 custom element 首次被插入文档 DOM 时,被调用。disconnectedCallback:当 custom element 从文档 DOM 中删除时,被调用。adoptedCallback:当 custom element 被移动到新的文档时,被调用。attributeChangedCallback: 当 custom element 增加、删除、修改自身属性时,被调用。
它和 Vue 还有 React 比较大的区别,我认为是在它的 JS 逻辑里,它需要和大量的和 DOM 进行“硬碰硬”,一下子又回到了刀耕火种的年代,而不是 Vue 和 React 的数据驱动 UI 的理念,比如,因为没有“响应式”,需要组件内部更新的数据你需要反映到真实的 DOM 操作中
Shadow DOM
Shadow DOM 选手其实在 <video> 标签里就出现过了,所以 Shadow DOM 最早的历史可以追溯到 2014 制定的 HTML5 的 <video> 标签里(当然也可能比这个长)
其实 Shadow DOM 可以理解为只有 <body> 的 iframe,里面只有 DOM Tree,它是独立于其它组件外部元素的 DOM Tree,有点 BFC 的感觉,当然 Shadow DOM 明显要更强,Shadow DOM 的元素不存在样式污染,样式制定可以随心所欲,同时里面的元素随便改动都不会触发组件外的重绘和重排
实际表现如下

对于样式污染的实际表现,可以参考下面这个例子(同时定义两个相同名称的 css 样式)
<head>
<script defer src="./main.js"></script>
</head>
<body>
<div class="text">Hello World</div>
<style>
.text {
color: blue;
}
</style>
<shadow-dom></shadow-dom>
<template id="shadow-dom">
<div class="text">Hello World</div>
<style>
.text {
color: red;
}
</style>
</template>
</body>
实际表现如下

相对于 css in js 或者是随机字符串前缀的样式污染解决方案,这种更优雅一点,毕竟是浏览器内部支持
总结
Web Component 渲染可以直接使用 DOM 而不需要类似 React 的 React.component() 也不需要 Vue 的 h(),所以它的渲染要更快一些,而没有框架的特性加持,维护 Web Component 需要自行编写 DOM 操作,如果是一个组件库的话可能会有一定工作量