之前就有在上一次更文挑战中看到掘金博主介绍过 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
操作,如果是一个组件库的话可能会有一定工作量