聊聊什么是 Web Components
摘要
组件化开发已经成为前端开发的主流趋势,目前流行的Vue和React都是组件化框架。Web Components API是谷歌在推动的浏览器原生组件,相比第三方框架,原生组件更为简单清晰,符合通用开发思维,不用加载任何外部模块,代码量较少。目前它还在不断发展,已经可用于生产环境。
Web Components 简介
Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.
翻译过来就是:
Web Components 是一套不同的技术,允许您创建可重用的自定义元素(其功能与其余代码封装在一起)并在您的 Web 应用程序中使用它们。
什么是Web Components
简单来说,Web Components是浏览器环境提供的一套api和模版,支持原生实现组件化,开发者可以方便的创建可重用的定制元素。其诞生主要是是为了解决代码复用、组件自定义、复用管理等问题,它通过标准化非侵入的方式封装一个组件,每个组件能组织好它自身的 HTML 结构、CSS 样式、JavaScript 代码,通过Shadow DOM 在文档中创建一个完全独立于其他元素的DOM树,主文档和基于Shadow DOM创建的独立组件之间是互相不干扰的。
Web Components主要由三项主要技术组成,分别是:
- Custom elements:一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
- Shadow DOM:一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
- HTML templates:
<template>
和<slot>
元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
如何创建一个Web Components
- 创建一个类或函数来指定web组件的功能,推荐使用类Class语法
- 使用 CustomElements.define() 方法注册自定义元素 ,并向其传递要定义的元素名称、指定元素功能的类、以及可选的其所继承自的元素。
- 使用Element.attachShadow() 方法将一个Shadow DOM附加到自定义元素上。使用原生的DOM方法向Shadow DOM中添加子元素、事件监听器等
- 使用
<template>
和<slot>
定义一个HTML模板。再次使用常规DOM方法克隆模板并将其附加到Shadow DOM中 - 在页面中使自定义元素,就像使用原生HTML元素一样
Web Components实践
<user-card avatar=URL name="Friday" email="friday@gmail.com"></user-card>
这种自定义的 HTML 标签,称为自定义元素(custom element)。自定义元素的名称必须包含连词线,用与区别原生的 HTML 元素,所以<user-card>
不能写成<usercard>
自定义HTML模板(template)
使用JavaScript来构建DOM 结构很麻烦,Web Components API 提供了<template>
标签,可以在它里面直接编写HTML片段来定义 DOM。代码如下图,此代码中暂未加入样式,稍后将会引入外部样式表。
<template id="userCardTemplate">
<div>
<div>
<div>
<div>
<img id="avatar" class="avatar" src="" width="128" height="128" />
</div>
<div>
<h1 id="name"></h1>
<p id="email"></p>
<button id="btn" type="button">Hello</button>
</div>
</div>
</div>
</div>
</template>
注册自定义元素 CustomElements.define()
自定义元素需要使用 JavaScript 定义一个类,所有<user-card>
都会是这个类的实例,如图所示UserCard是自定义元素的类,此类继承于HTMLElement,因此也会继承HTML元素的所有特性,接着使用window.customElements.define()
来将<user-card>
与此类关联。
上面代码中,获取<template>
节点以后,克隆了它的所有子元素,这是因为可能有多个自定义元素的实例,这个模板还要留给其他实例使用,所以不能直接移动它的子元素。
class UserCard extends HTMLElement {
constructor() {
super();
const templateElem = document.getElementById('userCardTemplate');
const content = templateElem.content.cloneNode(true);
this.appendChild(content);
}
}
window.customElements.define('user-card', UserCard);
添加样式
<template>
标签内里面可以编写任意HTML片段,因此我们可以直接在内引入外部样式表,也可以插入<style>
标签,并在其中编写样式,本次示例使用了GitHub官方的Primer CSS,需要注意的是获取自定义元素本身根节点时,应该使用:host选择器,加入样式后模板内容如下:
<template id="userCardTemplate">
<link href="./primer.css" rel="stylesheet">
<style>
.Box {
width: 500px;
}
</style>
<div class="Box color-shadow-medium">
<div class="Box-body">
<div class="d-flex flex-row flex-items-center">
<div class="col-md-4 d-flex flex-md-items-start">
<img id="avatar" class="avatar" src="" width="128" height="128" />
</div>
<div class="col-md-8 d-flex flex-column flex-md-items-start">
<h1 id="name" class="text-normal lh-condensed"></h1>
<p id="email" class="h4 color-fg-muted text-normal mb-2"></p>
<button id="btn" class="btn btn-primary" type="button">Hello</button>
</div>
</div>
</div>
</div>
</template>
Shadow DOM
Web components的一个重要属性是封装——可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净、整洁。
class UserCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({
mode: 'closed'
});
const templateElem = document.getElementById('userCardTemplate');
const content = templateElem.content.cloneNode(true);
content.querySelector('#avatar').setAttribute('src', this.getAttribute('avatar'));
content.querySelector('#name').innerText = this.getAttribute('name');
content.querySelector('#email').innerText = this.getAttribute('email');
content.querySelector('#btn').onclick = () => this.showMsg('Hello there');
shadow.appendChild(content);
}
showMsg(msg) {
window.alert(msg)
}
}
window.customElements.define('user-card', UserCard);
其中Shadow DOM 接口是关键所在,它可以将一个隐藏的、独立的 DOM 附加到一个元素上。可以使用 Element.attachShadow()
方法来将一个根节点附加到任何一个元素上。它接受一个配置对象作为参数,该对象有一个 mode
属性,值可以是 open
,表示可以通过页面内的 JavaScript方法来获取 Shadow DOM,设置为closed
则获取到的为null
。为方便用户传入参数,我们将自定义元素<user-card>
的参数输入到具体的节点内,同时为组件内的按钮绑定点击事件。
至此,这个Web Components组件就完成了,整个过程中相比第三方框架来说,减少了很多复杂的API使用,使用原生JS+HTML即可轻松完成。
总结
Web Components API的提出确实解决了一些问题,尤其是在代码隔离方面。但是其实在属性和值的操作上依然还是用最原始的selector方式,没有提供更优雅的API,如果组件内容比较复杂的时候就不够方便,代码也不够简洁,这也可能是之后厂商优化的方向。整体来说,Web Components的发展前景还是很不错,本文仅做入门,更多高级用法可以参考MDN和谷歌官方教程。
转载自:https://juejin.cn/post/7254559214588280890