Web Components
Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的web应用中使用它们。
作为开发者,我们都知道尽可能多的重用代码是一个好主意。这对于自定义标记结构来说通常不是那么容易 — 想想复杂的HTML(以及相关的样式和脚本),有时您不得不写代码来呈现自定义UI控件,并且如果您不小心的话,多次使用它们会使您的页面变得一团糟。
Web Components旨在解决这些问题 — 它由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。
基本概念
- Custom elements(自定义元素):一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
- Shadow DOM(影子DOM):一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
- HTML templates(HTML模板):
<template>
和<slot>
元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。 - HTML导入 (HTML Imports)
实现web component的基本方法通常如下所示:
- 创建一个类或函数来指定web组件的功能,如果使用类,请使用 ES6的类语法。
- 使用
CustomElementRegistry.define()
方法注册您的新自定义元素 ,并向其传递要定义的元素名称、指定元素功能的类、以及可选的其所继承自的元素。 - 如果需要的话,使用
Element.attachShadow()
方法将一个shadow DOM附加到自定义元素上。使用通常的DOM方法向shadow DOM中添加子元素、事件监听器等等。 - 如果需要的话,使用
<template>
和<slot>
定义一个HTML模板。再次使用常规DOM方法克隆模板并将其附加到您的shadow DOM中。 - 在页面任何您喜欢的位置使用自定义元素,就像使用常规HTML元素那样。
实例教程
上面介绍了Web Components的一些基本概念,接下来配合这些基本概念,我们将配备一些实例。
下图是一个用户卡片。
本文演示如何把这个卡片,写成 Web Components 组件。网页只要插入下面的代码,就会显示用户卡片。
1 <user-card></user-card>
注意:这种自定义的 HTML 标签,称为自定义元素(custom element)。根据规范,自定义元素的名称必须包含连词线,用与区别原生的 HTML 元素。所以,<user-card>不能写成<usercard>。
customElements.define()
自定义元素需要使用 JavaScript 定义一个类,所有<user-card>都会是这个类的实例。
1 class UserCard extends HTMLElement { 2 constructor() { 3 super(); 4 } 5 }
上面代码中,UserCard就是自定义元素的类。注意,这个类的父类是HTMLElement,因此继承了 HTML 元素的特性。
接着,使用浏览器原生的customElements.define()方法,告诉浏览器<user-card>元素与这个类关联。
1 window.customElements.define('user-card', UserCard);
自定义元素的内容
自定义元素<user-card>目前还是空的,下面在类里面给出这个元素的内容。
上面代码最后一行,this.append()的this表示自定义元素实例。
完成这一步以后,自定义元素内部的 DOM 结构就已经生成了。
template标签
使用 JavaScript 写上一节的 DOM 结构很麻烦,Web Components API 提供了<template>标签,类似vue书写组件的方式,可以在它里面使用 HTML 定义 DOM。
然后,改写一下自定义元素的类,为自定义元素加载<template>
上面代码中,获取<template>节点以后,克隆了它的所有子元素,这是因为可能有多个自定义元素的实例,这个模板还要留给其他实例使用,所以不能直接移动它的子元素。
到这一步为止,完整的代码如下
添加样式
自定义元素还没有样式,可以给它指定全局样式,比如下面这样。
1 user-card { 2 /* ... */ 3 }
但是,组件的样式应该与代码封装在一起,只对自定义元素生效,不影响外部的全局样式。所以,可以把样式写在<template>里面,类似scss里面使用scoped的标签。
注意:上面代码中,<template>样式里面的:host伪类,指代自定义元素本身。
自定义元素的参数
<user-card>内容现在是在<template>里面设定的,为了组件复用,我们希望把它改成传递参数的形式:
那么<template>代码也相应改造
最后,改一下类的代码,把参数加到自定义元素里面
Shadow DOM
我们不希望用户能够看到<user-card>的内部代码,Web Component 允许内部代码隐藏起来,这叫做 Shadow DOM,即这部分 DOM 默认与外部 DOM 隔离,内部任何代码都无法影响外部。
自定义元素的this.attachShadow()方法开启 Shadow DOM,详见下面的代码。
上面代码中,this.attachShadow()方法的参数{ mode: 'closed' },表示 Shadow DOM 是封闭的,不允许外部访问。
至此,这个 Web Component 组件就完成了,完整代码可以访问这里。可以看到,整个过程还是很简单的,不像第三方框架那样有复杂的 API。
组件的扩展
在前面的基础上,可以对组件进行扩展。
(1)与用户互动
用户卡片是一个静态组件,如果要与用户互动,也很简单,就是在类里面监听各种事件。
1 this.$button = shadow.querySelector('button'); 2 this.$button.addEventListener('click', () => { 3 // do something 4 });
(2)组件的封装
上面的例子中,<template>与网页代码放在一起,其实可以用脚本把<template>注入网页。这样的话,JavaScript 脚本跟<template>就能封装成一个 JS 文件,成为独立的组件文件。网页只要加载这个脚本,就能使用<user-card>组件。