Web Components
如果问你开发项目的时候,为什么不手写原生JS,而是用现如今非常流行的前端框架呢?原因肯定非常多,比如良好的生态、数据驱动视图、模块化、组件化等等,Web Components就是为了解决组件化这个问题而诞生的,它是浏览器原生支持的组件化,不依赖于任何库或框架以及各种编辑打包工具,便可以在浏览器中运行。组件化的好处相必不便多说,大家都用过vue、react这类知名框架,懂得都懂。但是这类框架的组件化不是真正的组件化,虽然写代码时写得是组件化的代码,但是编译过后就不再是组件化了。
比如我们用Vue + Element UI开发了一套后台管理系统,Element UI提供的组件都是以el开头的,像<el-button>
,但是编译过后显示在页面上就不是el-button标签了,这有点类似于CSS预处理器,那些像Saas、Less在开发阶段定义的变量其实并不是真正的变量,而是伪变量,在编译过后就没有变量这个概念了,所以很难跟JS进行通信。同理,框架的组件化也都不是真正的组件化,各家都是各家的组件化标准,这就导致了生态的分裂。而且框架的组件化都是要靠编译才能实现的。并且非常依赖于这个框架,是一种共生的关系。就像我们写xxx.vue是vue的组件,它没办法在浏览器中运行。
通常来说浏览器厂商会吸收一些流行的前端框架的可取之处,然后推动其成为标准,并在浏览器中原生实现其功能。Web Components与现在非常流行的mvvm框架是一种可以共存的关系,而不是一种互斥的关系。像saas变量和css变量,两者可以完美的互补。
Web Components不是单一的技术,而是由4门技术组合来的。这四门技术分别是HTML Imports、HTML templates、Custom Elements、Shadow DOM。
Web Components 的历史
其实 Web Components 并不是近几年才出现的规范。
最早在 2011 年的时候 Google 就推出了 Web Components 的概念,也算是前端发展的早期了。那时候前端还处于百废待兴的一个状态,前端甚至都没有「组件化」的概念,但是就是这个时候 Google 就已经凭明锐的嗅觉察觉到「组件化」是未来发展的趋势,所以提出了 Web Components 。不过在最开始时 Google 也只是提出了这样一个概念,并没有去实现它,所以并没有出现太大的浪花。
2011 年 React 框架也诞生了。
到了 2013 年,Google 浏览器和 Opera 浏览器联合推出 Web Components 规范的 v0 版本。这也算是 Web Components 最早的版本了。
2013 年 React 框架开源。 2014 年 Vue 框架诞生,这里为什么要提到 Vue 框架了?因为 Vue 作者在创建 Vue 的时候大量参考了 Web Components 的语法。
在 2016 年 2 月, Shadow DOM 和 Custom Element 被并入 DOM 标准规范里面,而不再作为独立的规范存在。
在 2017 年 Google I/O 上,Polymer 框架发布2.0 版本,而这次升级的最主要意义就是将 Shadow Dom 和 Custom Elements 升级到 v1 版本,从而获得更多浏览器支持下一 代 Web Components 规范。
然后在 2018 年,Shadow DOM 和 Custom Element v2 在 Chrome、Safari、三星浏览器上已经支持,还被 Firefox 列为要支持的特性。
至此所有浏览器都实现了web components,不过终究来的太晚了,三大框架(Vue、React、Angular)早已瓜分市场,形成了三足鼎立的局面。不过随着时间推移,三大框架可能会使用web components来实现自己的组件化系统,而vue官方的脚手架vue-cli早已实现了将vue组件转换成web components的功能,而且一些组件库为了跨框架运行也是使用web components来实现。比如taro3中的基础组件为了能够让vue、react都能使用,特意使用web components来实现的基础组件。
所以 Web Components 并不是一个新的概念,它已经存在很长时间了,只是可能还没有全面的进入研发者的视野。
HTML Imports
HTML Imports 已经被废弃,如果想正常使用 HTML Imports 代码查看效果,可以安装低版本浏览器。
现在JS模块化有ES Module、require(),CSS模块化有CSS Module、@import,从来没听过html有模块化的,也正是html imports为html带来了模块化的特性,导致了很多人觉得它很好用,但是被废弃了怎么办呢?现在有一个提案叫HTML Modules,就是用来代替HTML imports。
新的 HTML Modules 提案能够直接将 HTML 文件*作为 ECMAScript Module 引入:
import { libDom, libHelper } from './my-lib.html'
而在 HTML Module 文件类似于一个普通的局部 HTML:
<div id="blogPost">
<p> Some Amazing Content </p>
</div>
<script>
let blogPost = import.meta.document.querySelector("#blogPost")
export { blogPost }
</script>
当然也有一些细节需要注意:
- HTML 自身被解析为 DocumentFragment,并作为 default export(可覆盖)
- 文件中所有的 Inline Script 都是 Module*(不论是否指定 type),而非 Script
- Inline Script 可通过 import.meta.document 访问当前的 DocumentFragment
Custom Elements
从名字来看,就知道 Custom Elements 是用来创建自定义 HTML 标签。Cumtom elements 这个概念对于写过 Vue、React、Angular 的开发者而言应该非常的熟悉,在框架中通过组件的形式,使用自己定义的标签。
Web Component使用 CustomElementRegistry.define方法用来注册一个 custom element。
customElements.define(name, constructor, options);
- name:表示创建的元素的名称,不能是单个单词,必须要有短横线
- constructor:定义元素行为的类
- options(可选):配置对象
基于以上定义,我们可以实现一个自定义标签。
<template id="my-custom">
<p>我是一个自定义标签。</p>
</template>
<my-custom>
</my-custom>
<script>
class MyCustom extends HTMLElement {
constructor() {
super();
let template = document.getElementById('my-custom');
let templateContent = template.content;
const shadowRoot = this.attachShadow({mode: 'open'})
.appendChild(templateContent.cloneNode(true));
}
}
customElements.define('my-custom', MyCustom);
</script>
生命周期
在组件化的开发过程中有一个无论如何都绕不开的话题就是生命周期,生命周期对于开发者而言十分重要,我们在初始化阶段设置监听器,在组件挂载阶段设置dom元素,在组件更新阶段发送ajax请求,组件卸载的时候做取消定时器的操作。web components生命周期如下:
- connectedCallback:当 custom element 首次被插入文档 DOM 时,被调用。
- disconnectedCallback:当 custom element 从文档 DOM 中删除时,被调用。
- adoptedCallback:当 custom element 被移动到新的文档时,被调用。
- attributeChangedCallback: 当 custom element 增加、删除、修改自身属性时,被调用。