likes
comments
collection
share

聊聊什么是 Web Components

作者站长头像
站长
· 阅读数 50

摘要

组件化开发已经成为前端开发的主流趋势,目前流行的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

  1. 创建一个类或函数来指定web组件的功能,推荐使用类Class语法
  2. 使用 CustomElements.define() 方法注册自定义元素 ,并向其传递要定义的元素名称、指定元素功能的类、以及可选的其所继承自的元素。
  3. 使用Element.attachShadow() 方法将一个Shadow DOM附加到自定义元素上。使用原生的DOM方法向Shadow DOM中添加子元素、事件监听器等
  4. 使用<template><slot>定义一个HTML模板。再次使用常规DOM方法克隆模板并将其附加到Shadow DOM中
  5. 在页面中使自定义元素,就像使用原生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
评论
请登录