未来的组件化标准 —— 浅尝Web Components

Web Components涉及到的内容还是很多的,每一块都有很多东西可以讲,国外的好多大佬已经产出了好多优秀的文章。 本文照常只是简单了解大致内容而不进入深究,了解且会用即可,浅尝辄止。

简介

Web Components本身 不是一个 规范,而是由W3C提出的另外4个规范的合集。这四个规范是:

下面我们走马观花,简单了解一个这四个东西。

HTML Template

之前的页面开发经常的一个做法是把模板放在一个script标签或者隐藏的div中,用的时候通过innerHTML取出,塞进数据, 然后放回页面显示。现在我们可以通过<template>标签存放了。就像这样:

<template id="mytemplate">
 <img src="" alt="great image">
   <div class="comment"></div>
</template>
复制代码

特性检测

要特性检测 <template>,可以创建一个 template 元素并检查它是否拥有 content 属性:

function supportsTemplate() {
 return 'content' in document.createElement('template');
}

if (supportsTemplate()) {
   // 检测通过!
} else {
   // 使用旧的模板技术或库。
}
复制代码

激活模板

激活模板,即渲染出模板里面的内容。激活模板最简单的方法就是使用 document.importNode() 对模板的 .content 进行深拷贝。 .content 为只读属性,关联一个包含模板内容的 DocumentFragment。

var t = document.querySelector('#mytemplate');
// 在运行时填充 src。
t.content.querySelector('img').src = 'logo.png';

var clone = document.importNode(t.content, true);
document.body.appendChild(clone);
复制代码

特点

用 <template> 来包裹内容为我们提供了几个重要属性:

  • 它的内容在激活之前一直处于惰性状态。本质上,这些标记就是隐藏的 DOM,它们不会被渲染。

  • 处于模板中的内容不会有副作用。脚本不会运行,图片不会加载,音频不会播放,...直到模板被使用。

  • 内容不在文档中。在主页面使用 document.getElementById() 或 querySelector() 不会返回模板的子节点。

  • 模板能够被放置在任何位置,包括 <head>,<body>,或 <frameset>,并且任何能够出现在以上元素中的内容都可以放到模板中。 注意,"任何位置"意味着 <template> 能够安全的出现在 HTML 解析器不允许出现的位置... 几乎可以作为任何内容模型的子节点, 它也可以作为 <table> 或 <select> 的子元素。

推荐阅读

HTML Imports

之前在页面引入另一个页面或片段往往是通过iframe或者ajax异步加载,而现在我们可以这样做:

在head中引入

<head>
 <link rel="import" href="/path/to/imports/stuff.html">
</head>
复制代码

js中获取

var content = document.querySelector('link[rel="import"]').import;
复制代码

特性检测

要检测浏览器是否支持导入,可验证 <link> 元素上是否存在 import:

function supportsImports() {
 return 'import' in document.createElement('link');
}

if (supportsImports()) {
   // 支持导入
} else {
   // 使用其他方法加载文档
}
复制代码

推荐阅读

Shadow DOM

首先需要设置一下:打开开发者工具,f1打开设置(或右上角三个点),然后勾上Show user agent shadow DOM ——

未来的组件化标准 —— 浅尝Web Components
未来的组件化标准 —— 浅尝Web Components

然后再看下,video标签是这样的 ——

未来的组件化标准 —— 浅尝Web Components
未来的组件化标准 —— 浅尝Web Components

甚至一个普通的input ——

未来的组件化标准 —— 浅尝Web Components
未来的组件化标准 —— 浅尝Web Components

之前被隐藏掉的DOM部分就是shadow DOM。顾名思义,它是其宿主元素的影子,通常用来封装组件的内部结构。

所以像video、audio甚至input都是用简单的元素封装的组件。

这让我想到,我们是不是可以通过修改元素里面的shadow DOM的样式来改变该元素的样式呢? 答案是 —— 是的,但也 不完全 是...

未来的组件化标准 —— 浅尝Web Components
未来的组件化标准 —— 浅尝Web Components

从上图audio标签的结构和浏览器默认样式可以看到,我们可以像这样修改对应的样式:

audio::-webkit-media-controls {
 ...
}
复制代码

就像通过::-webkit-scrollbar改造浏览的滚动条样式那样,

于是,默认的audio样式(新版chrome)——

未来的组件化标准 —— 浅尝Web Components
未来的组件化标准 —— 浅尝Web Components

经过改造后,可以变成这样——

未来的组件化标准 —— 浅尝Web Components
未来的组件化标准 —— 浅尝Web Components

然而并不是所有样式都可以这样覆盖改造,像pseudo="-internal-media-controls-loading-panel"这样以"-internal-"开头的是不可以的。 所以这个做法还是有很大局限性的。

这是我试出来的,并没发现相关标准或依据...:sweat:

demo地址

这种做法自认为只适合拿来玩玩而已,不适合投入到项目开发中去。一来是因为其局限性太大,二来谁知道啥时候浏览器升级,这些标签的内部结构就又变化了呢, 最重要的是shadow DOM是为web Components而生的,与Custom Elements一起是web Components的重要组成部分,并非用于此“旁门左道”:laughing:。

推荐阅读

Custom Elements

自定义元素,首先有个硬性规定,自定义元素的命名中必须要有中划线“-”,否则便是未知元素了。

自定义元素分为两种 ——

自特性主自定义元素(Autonomous custom elements)

不具备任何已有元素的,其样式和行为完全自定义,如我们要定义一个这样的元素:

<flag-icon country="cn"></flag-icon>
复制代码

通过给属性country赋值来显示对应的国旗。

js的基本结构是这样的

class FlagIcon extends HTMLElement {
   constructor() {
     super();
     this._countryCode = null;
   }

   static get observedAttributes() { return ["country"]; }

   attributeChangedCallback(name, oldValue, newValue) {
     // name will always be "country" due to observedAttributes
     this._countryCode = newValue;
     this._updateRendering();
   }

   connectedCallback() {
     this._updateRendering();
   }

   get country() {
     return this._countryCode;
   }

   set country(v) {
     this.setAttribute("country", v);
   }

   _updateRendering() {
     //...
   }
}

//全局注册该元素
customElements.define("flag-icon", FlagIcon);
复制代码

注册后,也通过js创建该元素

const flagIcon = document.createElement("flag-icon");
flagIcon.country = "cn";
document.body.appendChild(flagIcon);
复制代码

自定义内置元素(Customized built-in elements)

继承自已有元素,拥有已有元素的所有特性。

比如我们自定义一个按钮,集成普通按钮所有的特性,但是当点击的时候会有一个动效,就可以这么做 ——

class PlasticButton extends HTMLButtonElement {
   constructor() {
     super();

     this.addEventListener("click", () => {
        // 动效逻辑
     });
   }
}
复制代码

不同的是,注册时要加上一个参数

customElements.define("plastic-button", PlasticButton, { extends: "button" });
复制代码

使用时也稍有不同

<button is="plastic-button">点我!</button>
复制代码

通过js定义元素,则是这样

const plasticButton = document.createElement("button", { is: "plastic-button" });
plasticButton.textContent = "点我!";
document.body.appendChild(flagIcon);
复制代码

生命周期

用过Vue、React等框架的同学对生命周期应该不陌生。同样,自定义元素有4个生命周期:

connectedCallback

元素首次被插入文档DOM时触发

disconnectedCallback

元素从文档DOM中删除时触发

adoptedCallback

元素被移动到新的文档时触发

attributeChangedCallback

元素增加、删除、修改自身属性时触发

推荐阅读

来一个demo

评分组件相信大家都司空见惯了。照葫芦画瓢,我用原生js写了一个Web Components 版的,简单实现了该组件的基本功能。

demo截图:

未来的组件化标准 —— 浅尝Web Components
未来的组件化标准 —— 浅尝Web Components

demo地址