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

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

原文作者:Potch

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

译者水平有限,如果有错误欢迎指正!

背景

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

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

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

标准发展

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

在标准委员会工作了一段时间之后,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 的未来在你手中,使用它来创造奇迹吧!

转载于:https://juejin.im/post/5c4a972b6fb9a049a7122eb7

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值