一、什么是 Web Components
一句话概括 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.
这段引用自MDN 的描述,其中关键的一点我进行了加粗标记:可重复使用的自定义元素。
Web Components 是一套可以创建可重复使用的自定义元素的技术。
几个概念
- Custom elements: 一组允许你创建自定义元素以及自定义元素行为的 Javascript API
- Shadow DOM: 一组把封装的"shadow" DOM 树关联到制定元素的Javascript API**
** - HTML templates:
**<template>**和**<slot>**能够然你编写不在页面重显示的标记模板
实现web component的基本方法通常如下所示:
- 创建一个类或函数来指定web组件的功能,如果使用类,请使用 ECMAScript 2015 的类语法。
- 使用
CustomElementRegistry.define()方法注册您的新自定义元素 ,并向其传递要定义的元素名称、指定元素功能的类、以及可选的其所继承自的元素。 - 如果需要的话,使用
Element.attachShadow()方法将一个shadow DOM附加到自定义元素上。使用通常的DOM方法向shadow DOM中添加子元素、事件监听器等等。 - 如果需要的话,使用
<template>和<slot>定义一个HTML模板。再次使用常规DOM方法克隆模板并将其附加到您的shadow DOM中。 - 在页面任何您喜欢的位置使用自定义元素,就像使用常规HTML元素那样。
以上的描述,主要来自于 MDN。那我们如何基于 Web Components 来写一个 Toast 组件呢?
二、如何基于 Web Components 写一个 Toast 组件
一开始我跟着文档进行了相应的示例尝试和预览。然后思考,如果我要是编写一个通用的第三方的 Web Compoennts 组件该如何去实现。所以选择了 rollup 进行了尝试并且发布到npm仓库上,主要想对比跟目前主流的单页应用的组件写法。
1、初始化 npm 项目
2、安装配置 rollup :详细见 Rollup 官网
3、编码实现
Toast 组件的模板结构,为了简便起见,定义得比较简单
<div><span>This is toast content!</span></div>
3.1 创建一个类或函数来指定web组件的功能
// 创建一个类或函数来指定web组件的功能
class Toast extends HTMLElement {
constructor() {
// 必须首先调用 super方法
super();
// 调用初始化方法
this.init()
}
init() {
// 初始化组件相关
// 创建 shadow root
// 创建组件模板 this.createTemplate()
// 创建组件样式 this.createStyle()
}
}
export default Toast;
custom elements 共有两种:
- Autonomous custom elements 是独立的元素,它不继承其他内建的HTML元素。你可以直接把它们写成HTML标签的形式,来在页面上使用。例如 ,或者是document.createElement("webc-toast")这样。
- Customized built-in elements 继承自基本的HTML元素。在创建时,你必须指定所需扩展的元素,使用时,需要先写出基本的元素标签,并通过 is 属性指定custom element的名称。例如
, 或者 document.createElement("p", { is: "webc-toast" })。
3.2 使用 CustomElementRegistry.define() 方法注册自定义元素
customElements.define('webc-toast', Toast);
至此,我们已经创建了一个名为"webc-toast"的自定义组件。并且我们可以在页面重直接使用自定义的元素名称来使用:<webc-toast></webc-toast>
3.3 创建一个关联到当前组件的shadow root
// 创建一个 shadow root. mode 是指是否可以从外部通过元素访问到
// element.shadowRoot; 返回一个ShadowRoot对象const shadow = this.attachShadow({mode: 'open'});
3.4 创建组件模板,并添加相应的样式名称
createTemplate() {
// Toast 结构 <div><span>This is toast content!</span></div>
const wrapper = document.createElement('div');
wrapper.setAttribute('class', 'c-toast-wrapper');
const contentWrapper = document.createElement('span');
contentWrapper.setAttribute('class', 'c-toast-wrapper__cwrapper') // 获取content属性上的内容,并添加到span标签内
const textContent = this.getAttribute('content');
contentWrapper.textContent = textContent;
wrapper.appendChild(contentWrapper);
return wrapper;
}
3.5 创建组件样式
createStyle() {
const show = this.getAttribute('show');
console.log(show)
// 创建一些 CSS,并应用到 shadow dom上
const style = document.createElement('style');
style.textContent = `
.c-toast-wrapper {
box-sizing: content-box;
padding: 5px 20px;
text-align: center;
position: fixed;
left: 50%;
transform: translate(-50%, 0);
top: 40px;
border: solid 1px #eee;
border-radius: 10px;
user-select: none;
color: #0060ff;
border-color: #0060ff;
background-color: #fff;
}
`;
return style;
}
3.6 组合模板样式并添加到 shadow root 上
init() {
// 自定义元素的功能代码写在这里
// 创建一个 shadow root
const shadow = this.attachShadow({mode: 'open'});
const template = this.createTemplate();
// 创建一些 CSS,并应用到 shadow dom上
const style = this.createStyle();
// 将创建的元素附加到 shadow dom
shadow.appendChild(style);
shadow.appendChild(template);
}
至此,我们就完成了整个组件的编写。Github 地址
不过我们目前还没有使用到 template 以及 slot
4、生命周期函数
connectedCallback:当 custom element首次被插入文档DOM时,被调用。disconnectedCallback:当 custom element从文档DOM中删除时,被调用。adoptedCallback:当 custom element被移动到新的文档时,被调用。attributeChangedCallback: 当 custom element增加、删除、修改自身属性时,被调用。
5、问题思考
- 如何开发调试组件?
- 样式预编译支持?
- 事件处理?
- 模板如何开发维护?