Web Components
放眼前端技术发展历程,代码复用和模块化是永远绕不开的话题。
我们的 JavaScript,IIFE 模式和原型模式是其逻辑复用的基本能力。近几年 CMD,AMD,CommonJS 等各种规范对 JS 模块化持续加成,ES6 的定版更是官方爸爸的大力加持。
CSS 则有 @import url
,更别说Sass、Less、PostCss等功能强大的预编译器了,相比起来 HTML 则就略显惨淡。
当然,在复用和组件化的潮流中 HTML 也不是那么的格格不入,毕竟时下最流行的 Vue,React,Sevlte 都是实现自定义 HTML 组件强有力框架。然而框架各自为阵,语法和使用方式不同,学习成本不同,总不是长久之计。
那么我们来看看 Web Components 有什么不同的思想呢?
一、基础知识
Web Components 它由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。
- Custom element(自定义元素) :一组 JavaScript API,允许你定义 custom elements 及其行为,然后可以在你的用户界面中按照需要使用它们。
- Shadow DOM(影子 DOM) :一组 JavaScript API,用于将封装的"影子"DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,你可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
- HTML template(HTML 模板):
<template>
和<slot>
元素使你可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
看到 <template>
和 <slot>
是不是很熟悉,没错,Vue 的模板语法正是尤大借鉴了 Web Component 思想, 那么下面我们以一个实例来深入探讨:
二、如何使用
然而事情并没这么简单,仔细查看 SelfComponent
组件会发现我们只是单纯的把 template
内容挂在 body 上,一点也不优雅;而且说好的 shadow DOM
呢,咋也没见着。
我们思考以下两个问题:
a.怎么把组件脱离真实 DOM,做到内部独立?
b.组件如何做到内容自定义?
三、shadow DOM 改造
针对第一个问题,其核心是希望自定义组件做到内部独立(样式,事件等),借用 shadow 可以实现这一目标。定义 shadow DOM 只需要一个方法即可:Element.attachShadow(shadowRootInit)
,其中 shadowRootInit
是一个字典对象,包含两个值:mode 和 delegatesFocus;具体内容详见 attachShadow。
我们将刚才的组件进行改造:
scala
class SelfComponent extends HTMLElement {
constructor() {
super();
let shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(art.content.cloneNode(true));
}
}
我们借助了 attachShadow
创建了一个 shadowRoot 对象,然后把模板内容挂在 shadowRoot
上。上面说过 shadowRoot 有私有性,下面我们验证一下:
可以看到自定义元素外的 article 并没有应用上边框和背景样式效果。
四、template 和 slot 的妙用
上面的自定义组件还没有实现内容自定义,那么这时就要请出我们的 slot 插槽。插槽具有一个 name 属性作为标识:
可以发现内容已经可以自定义了!
五、Web Components 进阶
通过上面简单的例子,我们对 Web Compoents 的基本功能大致掌握了。但上面的例子很多简单,不能满足我们复杂的需求,还会有以下问题:
- 想随意操作模板的内容,给它添加事件啥的。。。
- 自定义组件确实有了,但我想在组件不同的阶段做不同的事情,怎么搞呢?
- slot 看着挺有用,但它使用规则定义的太死了,不太灵活
Custom elements
前文我们只讲到如何创建一个自定义元素,然后将其挂载到真实 DOM 或者 shadow DOM 上。接下来进一步看看它还有哪些其他特别的功能。
1.定义和使用
你可以通过 CustomElementRegistry.define()
返回的绑定在 window 对象上的 customElements
实例进行元素自定义。其语法为 customElements.define(name, constructor, options);
- name : 元素名。只能用短线连接的英文字符,不能是单个单词。例:name='my-comp'
- constructor:元素构造器。必须继承自
HTMLElement
或其子类(例如:HTMLParagraphElement 等) - options:【可选】控制元素如何定义,只支持一个选项
extends
,用于指明定义的元素继承自何种类型元素。(例如:{ extends: 'p' })
前两个参数应该比较好理解,第三个参数特别要注意:extends 指明的元素和 constructor 继承的元素保持一致
2.生命周期
在custom element的构造函数中,可以指定多个不同的回调函数,它们将会在元素的不同生命时期被调用:
- connectedCallback:当 custom element 首次被插入文档DOM时,被调用。
- disconnectedCallback:当 custom element 从文档DOM中删除时,被调用。
- adoptedCallback:当 custom element 被移动到新的文档时,被调用。
- attributeChangedCallback: 当 custom element 增加、删除、修改自身属性时,被调用
具体的使用方法可以参考 MDN
3. Shadow DOM WebComponents最重要特性,它才真正实现了 HTML 的复用 ------ 将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离。我们元素查看Web Components 组件: shadow-root
节点,在此节点内部的样式和行为都不会影响外面元素。
还有一点要特别注意的事,不是任何类型的元素都可以附加到 shadow root 下面。出于安全考虑,目前只有这些类型元素可以附加到 shadow DOM 上(有效的自定义元素也可):可附加的元素
4. template 和 slot
插槽分为具名和非具名, 使用方法和我们的Vue的基本一致, 这里就不赘述了。
上面说了那么多, 发现好像并不能让我们在各种需求中用到, 那么我们下面就了解下在Vue中如何使用Web Components。
六、Web Components + Vue
默认情况下,Vue 会将任何非原生的 HTML 标签优先当作 Vue 组件处理,而将"渲染一个自定义元素"作为后备选项。这会在开发时导致 Vue 抛出一个"解析组件失败"的警告。要让 Vue 知晓特定元素应该被视为自定义元素并跳过组件解析,我们可以指定 compilerOptions.isCustomElement
这个选项。
可以看到Web Component已经渲染出来, 要注意的是, CustomElement 好像不能使用 setup
语法糖。