基于 Web Components 编写一个 Toast 组件

1,042 阅读5分钟

一、什么是 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的基本方法通常如下所示:

  1. 创建一个类或函数来指定web组件的功能,如果使用类,请使用 ECMAScript 2015 的类语法。
  2. 使用 CustomElementRegistry.define() 方法注册您的新自定义元素 ,并向其传递要定义的元素名称、指定元素功能的类、以及可选的其所继承自的元素。
  3. 如果需要的话,使用Element.attachShadow() 方法将一个shadow DOM附加到自定义元素上。使用通常的DOM方法向shadow DOM中添加子元素、事件监听器等等。
  4. 如果需要的话,使用 <template><slot> 定义一个HTML模板。再次使用常规DOM方法克隆模板并将其附加到您的shadow DOM中。
  5. 在页面任何您喜欢的位置使用自定义元素,就像使用常规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、问题思考

  1. 如何开发调试组件?
  2. 样式预编译支持?
  3. 事件处理?
  4. 模板如何开发维护?