[译] 组件化开发利器:Web Components标准

原文地址:hacks.mozilla.org/2018/11/the…javascript

原文做者:Potchcss

2018年11月15日发表于 Developer Tools, DOM, Featured Article, 以及 Web Componentshtml

译者水平有限,若是有错误欢迎指正!前端

背景

自从第一个动态的 DHTML 光标拖拽的诞生,以及“本周网站”的徽章为网站增色,可复用代码对 web 开发者极具诱惑力。可是在本身的网站中引入三方 UI 组件一直是一个比较头疼的事情。java

引入别人造好的轮子会带来不少 javascript 和 css冲突,想一想那些可怕的 !important 吧。使用现代前端框架好比React可能会好一些,可是为了为了重用一些组件而引入一个框架显然是有些笨重。 HTML5 把一些经常使用的组件引入 web 标准,像 <video><input type="date" />,可是,为每一个经常使用Web UI库添加新标准标签并非一个可持续维护的方式。es6

这时,一些 web 标准草案就应运而生了。每一个标准有其独立的功能,可是把他们组合在一块儿,就能解决以前不能用原生方案解决的问题,而且它们很是难伪造,由于自定义 HTML 组件能够像传统 HTML 标签同样使用。这些组件把复杂的实现封装在内部,就像富文本编辑器和视频播放器同样。web

标准发展

总体来讲,这组标准就是 Web Components。在2018年前端组件化并非什么新鲜事物。的确,从2014年开始,chrome一直以这样或那样的方式实现这些标准,其余浏览器也有相应的polyfills。chrome

在标准委员会工做了一段时间以后,Web Components 标准从早期的形式(现称为version 0版本)演变成了更成熟的version 1版本,而且被主流浏览器实现。Firefox63 对此增长了两个支持:Custom Elements、shadow DOM。如今一块儿来看看怎么扮演 HTML 的发明家吧!浏览器

Web Components已经存在了一段时间,相关资源比较多。本文只做为初级读物,介绍一些列的新性能和资源,若是你想了解更多(你也应该了解更多),请移步 MDN Web DocsGoogle Developers安全

自定义 HTML 标签须要浏览器之前没赋予开发者的新功能。我将在每一单元列出这些从前不能实现的地方,以及他们所使用的其余 web 新技术。

<template> 标签: 一个小复习

第一个标签是老朋友了,它知足的需求早于 Web Components。有时你只想存储一些 HTML。也许有时你要屡次复制标签,也许有时你还不想立刻建立一个UI。<template> 标签包含并解析 HTML ,但不把解析出的 DOM 添加到当前文档中。

<template>
    <h1>This won't display!</h1> <script>alert("this won't alert!");</script> </template> 复制代码

那么解析出的 DOM 去哪了呢?它被添加到了“文档碎片”中,能够把它理解成一个包含 html 的薄容器。当被添加到 DOM 中时文档碎片就解体了,当你想保留一组稍后使用的标签,又不想保留其容器时,文档碎片很是有用。

“那么,我该怎么使用一个正在解体的容器中的标签呢?”

答案是:你只需把模版的文档碎片插入当前文档便可:

let template = document.querySelector('template');
document.body.appendChild(template.content);
复制代码

上面这段代码能够正常执行,可是若是你刚解体了文档碎片就会报错!若是你重复运行上述代码就会报错。由于第二次运行时 template.content 已经没有了。咱们应该用一个碎片的拷贝代替 template.content,而后再插入这个拷贝,代码以下:

document.body.appendChild(template.content.cloneNode(true));
复制代码

cloneNode 方法顾名思义,接收一个参数控制只拷贝标签自己仍是包括它的子标签。

新知识点:

  • <template> 标签包含 HTML,可是不向当前文档添加。

总结:

Custom Elements

Custom Elements是 Web Components标准的表明。它确实让开发人员实现了自定义 HTML 标签。这一切的实现得益于 ES6 的 class 语法糖。若是你对 javascript 或者其余面向对象语言很熟悉的话,你能够像这样经过继承来实现本身的类:

class MyClass extends BaseClass {
    // class definition goes here
}
复制代码

咱们来试一下这样写:

class MyElement extends HTMLElement {}
复制代码

不久以前这样写还会报错。浏览器不容许原生 HTMLElement 类或其子类被继承。Custom Elements 解除了这一限制。

浏览器会把 <p> 标签映射到 HTMLParagraphElement 原生类,可是它怎么映射自定义类呢?除了继承内部类外,还有一个“自定义标签注册表”用于声明这种映射:

customElements.define('my-element', MyElement);
复制代码

如今页面上的每一个 <my-element> 标签都与一个 MyElement 元素对应。 页面每解析一个 <my-element> 标签就调用一次 MyElement 的构造函数。

为何标签名带中横线呢?标准制定者但愿将来开发者能够自由的自定义标签,这意味着开发者均可以建立 <h7> 或者 <vr> 这样的标签。为了不将来的冲突,全部自定义标签必须加中横线,同时原生 HTML 标签保证毫不包含中横线。问题解决!

除了标签建立时会调用构造函数,还有一系列生命周期函数会在特定时刻被调用:

  • connectedCallback 当元素被添加到文档中时调用。这个函数可能屡次调用,好比标签移动、移除或从新添加时。
  • disconnectedCallbackconnectedCallback 相对应。
  • attributeChangeCallback 元素属性更改时调用。

下面是一个稍复杂的例子:

class GreetingElement extends HTMLElement {
  constructor() {
    super();
    this._name = 'Stranger';
  }
  connectedCallback() {
    this.addEventListener('click', e => alert(`Hello, ${this._name}!`));
  }
  attributeChangedCallback(attrName, oldValue, newValue) {
    if (attrName === 'name') {
      if (newValue) {
        this._name = newValue;
      } else {
        this._name = 'Stranger';
      }
    }
  }
}
GreetingElement.observedAttributes = ['name'];
customElements.define('hey-there', GreetingElement);
复制代码

在页面上这样使用:

<hey-there>Greeting</hey-there>
<hey-there name="Potch">Personalized Greeting</hey-there>
复制代码

若是要继承一个 HTML 原生标签,你可能会想定义一个看起来彻底不一样新标签。好比让 <hey-there> 去继承 <button>

class GreetingElement extends HTMLButtonElement
复制代码

同时要在自定义标签注册表中体现出继承一个已有标签:

customElements.define('hey-there', GreetingElement, { extends: 'button' });
复制代码

咱们应该用被继承的标签加 is 属性来表示这种继承关系,而不是直接用自定义标签,咱们这样使用继承 <button><hey-there> 标签:

<button is="hey-there" name="World">Howdy</button>
复制代码

这不是画蛇添足,这样程序就会知道 <hey-there> 是继承的 <button>

这些对全部的传统 web 标签都适用。咱们可使用 <template> 设置一系列事件处理程序,添加自定义样式,甚至能够封装一个内部结构。其余人能够经过 HTML 标签、 DOM 调用、或者新框架(其中一些框架支持在虚拟 DOM 中自定义标签名)的方式在本身的代码中引用你的自定义组件。由于这些都是标准的 DOM 接口,因此 Custom Elements 实现了真正的可移植组件。

新知识点:

  • Custom Elements 能够继承原生 HTMLElement 类和其子类。
  • 经过 customElements.define() 维护自定义标签注册表。
  • 特定生命周期函数在标签建立、添加到DOM、属性被修改等时刻调用。

总结: ES6 Classes 特别是 子类和 extends 关键词

Shadow DOM

咱们写出了友好的 custom element,也为其添加了漂亮的样式。如今咱们想把它用在咱们的站点上,也想把代码分享出去,让更多的人用在他们的网站上。可是咱们怎么避免自定义 <button> 标签和其余网站的 css 冲突?答案是使用 Shadow DOM。

Shadow DOM 标准提出了 shadow root 的概念。shadow root 有标准的 DOM 方法,也能够像其余 DOM 节点同样添加到文档中。shadow root 的亮点在于其内容不会出如今包含其父节点的文档中:

// attachShadow creates a shadow root.
let shadow = div.attachShadow({ mode: 'open' });
let inner = document.createElement('b');
inner.appendChild(document.createTextNode('Hiding in the shadows'));
// shadow root supports the normal appendChild method.
shadow.appendChild(inner);
div.querySelector('b'); // empty
复制代码

在上面的例子中,<div> 包含 <b> 而且 <b> 标签也渲染在了页面上,可是常规的 DOM 方法却找不到它。不只如此,页面的样式也影响不到它。这意味着 shadow root 既不受外部样式影响,其内部样式也不会泄漏。但这边界不涉及安全性,页面上的 js 能够检测到 shadow root 的建立,经过 shadow root 的引用,能够查询到它里面的内容。

为 shadow root 里的内容设置样式能够经过给根节点添加<style> (或者 <link>)标签:

let style = document.createElement('style');
style.innerText = 'b { font-weight: bolder; color: red; }';
shadowRoot.appendChild(style);
let inner = document.createElement('b');
inner.innerHTML = "I'm bolder in the shadows";
shadowRoot.appendChild(inner);
复制代码

如今咱们能够真正使用 <template> 标签了!无论用哪一种方法,shadow root 内部的 <b> 标签样式只会被根标签上的样式控制,不会受外部影响。

若是 custom element 不使用 shadow DOM 怎么办?咱们依然可使用一个新标签 <slot>

<template>
  Hello, <slot></slot>!
</template>
复制代码

若是这个模板被添加到一个 shadow root 中,那么下述标签:

<hey-there>World</hey-there>
复制代码

将被渲染为:

Hello, World!
复制代码

这种将 shadow DOM 和非 shadow DOM 整合使用的功能,能够把 custom element 复杂的实现封装在其内部,而把调用变的简单。slot的威力远不止这些,还有多重 slot、命名 slot、针对特定内容的 css 伪类 slot 等。建议查阅文档了解更多。

新知识点

  • 一种准屏蔽 DOM 结构 —— shadow root
  • 建立和访问shadow root 的 DOM API
  • shadow root 的样式做用域
  • 用于shadow root 和样式做用域的新 css 伪类
  • <slot> 标签

最终效果

最后来一块儿实现这个漂亮的按钮吧!咱们给这个按钮取名 <fancy-button>。它的奇妙之处在于,它有定制的样式,也容许咱们为它添加图标使它变得美观。咱们把样式封装在 shadow root 中,这样就能够保证在任何引用它的网站上样式保持不变。

你能够查看下面这个完整的交互型代码示例。请仔细查看 custom element 的 js 定义以及 <template> 标签的样式和结构。

点击查看完整示例

总结

Web Components标准创建在这样一种理念之上:提供多个底层功能,开发者以标准制定者不曾设想的方式把这些功能组合起来使用。Custom Elements 已经被用于在页面上建立 VR 内容富UI工具等,并使这些变得简单。尽管标准的敲定过程很漫长,Web Components 标准为 Web 开发者提供了更多的可能。现代浏览器已经支持了这项技术,Web Components 的将来在你手中,使用它来创造奇迹吧!

相关文章
相关标签/搜索