- 从前端组件化到Web Components
- 主要的API与兼容性
- webcomponentsjs,AMP,Polymer
- react、vue、angular和web components
从前端组件化到Web Components
在 Github 等社区和绝大多数互联网公司,从未停止过对前端组件化的尝试。 这些尝试产生了大量的各式各样的组件化技术,它们在解决的问题粒度、 提倡的架构设计、编译和处理的时机等方面各有不同:
- 从预编译时进行处理的 ES6 模块,到运行时异步加载的 AMD 模块;
- 从简单的 RoR (Ruby on Rails)简单的服务器端 MVC 设计,到 AngularJS/Vue.js 的前端 MVVM;
- 从 LESS 等单个语言的预编译工具,到 webpack 这样的全站打包工具。
为什么前端组件化?
- 项目复杂度增加, 一个页面一个文件需要处理的内容过多.
- 重复性劳动多, 效率低
- 质量差, 不可控
前端组件化困难的一些原因
- 缺乏 JavaScript 模块化标准
- CSS 的全局作用域
- 全局 DOM 和 Window 的设计
现有的组件化的一些方案?
- 组件化初探
- jQuery 插件机制:
$(".select").pluginName(config)
- 对象模式,各类库, 如swiper.js:
<!-- Slider main container -->
<div class="swiper-container">
<!-- Additional required wrapper -->
<div class="swiper-wrapper">
<!-- Slides -->
<div class="swiper-slide">Slide 1</div>
<div class="swiper-slide">Slide 2</div>
<div class="swiper-slide">Slide 3</div>
...
</div>
</div>
<script src="path/to/swiper.min.js"></script>
<script>
var mySwiper = new Swiper ('.swiper-container', {
direction: 'vertical',
loop: true,
})
</script>
- 现代组件结构 前端框架 Vue React 已经是组件化的集大成者。
- 我们期望的组件 (1)可拓展性:既然组件是针对某一特定功能或需求开发的,那它就必须易于开发和拓展; (2)封装性:组件作为一个独立整体供使用,应该是高內聚低耦合的。遵守开放封闭原则,只供使用,而不对使用环境产生副作用; (3)易用性:组件的目的是产生可重用的独立部件,那就必须提供一种简单快捷的方式供使用。
- 组件的结构 (1)隔离的UI,包括内容,样式 (2)隔离的功能属性attribute,property,status(内部状态), method (3)各自的生命周期 生命周期通常有, 创建, 挂载, 销毁前等, vue 和 react 设计了更细粒度的生命周期. 为什么要有生命周期, 因为我们在设计好一个组件已经组件的功能时, 我们需要在一些特定的时候执行一些代码, 比如初始化动作, 获取数据动作等. 我们把做这些动作的时机整理后发现, 我们往往需要在创建的时候需要做一些动作, 在构建好组件的 dom 挂载到页面的时候需要做一些动作, 在销毁前需要做一些动作比如内存释放等. 因此这些时机也在现代框架中得到了标准的支持.
什么是Web Components?
Web Components 是 WHATWG 和 W3C 正在尝试的 Web 组件化方案,为组件化的 Web 前端开发提供浏览器级别的支持。最早的努力当数 “Web Component” 规范说明 circa 2011 的出现,并在同年的 Fronteers Conference 大会上由 Alex Russell 将之宣之于众。该 Web Component 规范的产生和发展,旨在提供一种权威的、浏览器能理解的方式来创建组件。
Web Components 是一系列 Web 技术的集合,主要包括 Shadow DOM、Custom Elements、HTML templates、HTML Import(废弃,HTML Modules替换)。
主要的技术与兼容性
Custom elements:可重用网络组件。
HTML 现行标准缺乏自动关联 JS 行为和标记的方法。自定义元素使 HTML 变得现代化;补充了缺少的部件,并将结构与行为相结合。 如果 HTML 无法为问题提供解决方案,我们可以创建自定义元素来解决。 自定义元素在保留 HTML 优点的同时为浏览器带来新功能。
- 有关创建自定义元素的规则
- 自定义元素的名称必须包含短横线 (-)。因此,、 和 等均为有效名称,而 和 <foo_bar> 则为无效名称。这一要求使得 HTML 解析器能够区分自定义元素和常规元素。它还可确保向 HTML 添加新标记时的向前兼容性。
- 您不能多次注册同一标记。否则,将产生 DOMException。让浏览器了解新标记后,它就这样定了下来。您不能撤回。
- 自定义元素不能自我封闭,因为 HTML 仅允许少数元素自我封闭。必须编写封闭标记 ()。
- 自定义元素
class AppDrawer extends HTMLElement {
// 类定义中的this引用DOM元素自身,即类的实例。
// 完整的DOM API在元素代码内提供。
// 使用this来访问元素属性、检验子项(this.children)和查询节点(this.querySelectorALl('.items'))等
...
}
window.customElements.define('app-drawer', AppDrawer);
// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer', class extends HTMLElement {...});
<app-drawer></app-drawer>
- 拓展自定义元素
class FancyDrawer extends AppDrawer {
constructor() {
super(); // always call super() first in the constructor. This also calls the extended class' constructor.
...
}
toggleDrawer() {
// Possibly different toggle implementation?
// Use ES2015 if you need to call the parent method.
// super.toggleDrawer()
}
anotherMethod() {
...
}
}
customElements.define('fancy-app-drawer', FancyDrawer);
- 扩展原生 HTML 元素
// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class FancyButton extends HTMLButtonElement {
constructor() {
super(); // always call super() first in the constructor.
this.addEventListener('click', e => this.drawRipple(e.offsetX, e.offsetY));
}
// Material design ripple animation.
drawRipple(x, y) {
let div = document.createElement('div');
div.classList.add('ripple');
this.appendChild(div);
div.style.top = `${y - div.clientHeight/2}px`;
div.style.left = `${x - div.clientWidth/2}px`;
div.style.backgroundColor = 'currentColor';
div.classList.add('run');
div.addEventListener('transitionend', e => div.remove());
}
}
customElements.define('fancy-button', FancyButton, {extends: 'button'});
<!-- This <button> is a fancy button. -->
<button is="fancy-button" disabled>Fancy button!</button>
扩展原生元素时,对 define() 的调用会稍有不同。所需的第三个参数告知浏览器要扩展的标记。这很有必要,因为许多 HTML 标记均使用同一 DOM 接口。例如,<section>、<address> 和 <em>(以及其他)都使用 HTMLElement;<q> 和 <blockquote> 则使用 HTMLQuoteElement;等等。指定 {extends: 'blockquote'} 可让浏览器知道您创建的是增强的 <blockquote> 而不是 <q>。有关 HTML DOM 接口的完整列表,请参阅 HTML 规范。
-
自定义元素的响应 自定义元素可以定义特殊生命周期钩子,以便在其存续的特定时间内运行代码。 这称为自定义元素响应。
-
总结:
自定义元素提供了一种新工具,可让我们在浏览器中定义新 HTML 标记并创建可重用的组件。 将它们与 Shadow DOM 和 <template> 等新平台原语结合使用,我们可开始实现网络组件的宏大图景:
- 创建和扩展可重复使用组件的跨浏览器(网络标准)。
- 无需库或框架即可使用。原生 JS/HTML 威武!
- 提供熟悉的编程模型。仅需使用 DOM/CSS/HTML。
- 与其他网络平台功能良好匹配(Shadow DOM、<template>、CSS 自定义属性等)
- 与浏览器的 DevTools 紧密集成。
- 利用现有的无障碍功能。
Shadow DOM — 对标签和样式的一层 DOM 包装
Shadow DOM 解决了构建网络应用的脆弱性问题。脆弱性是由 HTML、CSS 和 JS 的全局性引起的。 Shadow DOM 修复了 CSS 和 DOM。它在网络平台中引入作用域样式。 无需工具或命名约定,您即可使用原生 JavaScript 捆绑 CSS 和标记、隐藏实现详情以及编写独立的组件。
Shadow DOM 这款工具旨在构建基于组件的应用。因此,可为网络开发中的常见问题提供解决方案:
- 隔离 DOM:组件的 DOM 是独立的(例如,document.querySelector() 不会返回组件 shadow DOM 中的节点)。
- 作用域 CSS:shadow DOM 内部定义的 CSS 在其作用域内。样式规则不会泄漏,页面样式也不会渗入。
- 组合:为组件设计一个声明性、基于标记的 API。
- 简化 CSS - 作用域 DOM 意味着您可以使用简单的 CSS 选择器,更通用的 id/类名称,而无需担心命名冲突。
- 效率 - 将应用看成是多个 DOM 块,而不是一个大的(全局性)页面。
- Shadow DOM 与普通 DOM 相同,但有两点区别:
- 创建/使用的方式;
- 与页面其他部分有关的行为方式。 通常,您创建 DOM 节点并将其附加至其他元素作为子项。 借助于 shadow DOM,您可以创建作用域 DOM 树,该 DOM 树附加至该元素上,但与其自身真正的子项分离开来。这一作用域子树称为影子树。被附着的元素称为影子宿主。 您在影子中添加的任何项均将成为宿主元素的本地项,包括 <style>。 这就是 shadow DOM 实现 CSS 样式作用域的方式。
- 创建 shadow DOM (element.attachShadow())
const header = document.createElement('header');
const shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>'; // Could also use appendChild().
// header.shadowRoot === shadowRoot
// shadowRoot.host === header
规范定义了一些元素无法托管影子树,原因有2点:
- 浏览器已为该元素托管其自身的内部 shadow DOM(<textarea>、<input>)。
- 让元素托管 shadow DOM 毫无意义 (<img>)。
- 为自定义元素创建 shadow DOM
// Use custom elements API v1 to register a new HTML tag and define its JS behavior
// using an ES6 class. Every instance of <fancy-tab> will have this same prototype.
customElements.define('fancy-tabs', class extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
// Attach a shadow root to <fancy-tabs>.
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>#tabs { ... }</style> <!-- styles are scoped to fancy-tabs! -->
<div id="tabs">...</div>
<div id="panels">...</div>
`;
}
...
});
这里有几个有趣的事情。首先, 实例创建后,自定义元素创建其自身的 shadow DOM。这在 constructor() 中完成。其次,因为我们要创建一个影子根,因此 中的 CSS 规则将作用域仅限于 。
- 组合和slot 如果 引入了元素,则这些元素可“跨越” shadow DOM 的边界。 这些元素称为分布式节点。从概念上来看,分布式节点似乎有点奇怪。 Slot 实际上并不移动 DOM;它们在 shadow DOM 内部的其他位置进行渲染。
看代码。
HTML Templates and Slots — 可复用的 HTML 标签,提供了和用户自定义标签相结合的接口
HTML 模板是支持度最高的特性,可以说是 Web Component 规范最直观的体现。它允许开发者定义一个直到被复制使用时才会进行渲染的 HTML 标签块。
HTML Import(废弃,HTML Modules替换)
web components 相关的一些作品
webcomponentsjs
webcomponentsjs webcomponentsjs 项目为 Web Components 标准提供了一系列的 Polyfill。 包括 Custom Elements, Shady DOM、HTML Import 等机制,HTMLTemplateElement, Promise, CustomEvent 等对象。
web components基于标准实现,具有通用性,避免的不同框架的互斥性。
借助 webcomponentsjs,已经可以兼容 IE11+,Chrome,Firefox,Safari 9+ 等浏览器。 足以支持轻量地使用 Web Components 相关技术。 目前 Polyfill 文件(非 lite 版本)本身大小在 100k+,gzip 后可以缩小到 30k+。
AMP
polymer
polymer polymer项目是基于 Web Components 机制的轻量级框架,定位于简单的 Polyfill 和易用性封装。 这些封装包括数据绑定,模板声明,事件系统等,甚至包括手势事件的 API。
react、vue, angular与 web components
- react 与web components 你中有我,我中有你


-
vue 借鉴了一些 web components的概念设计, 通过vue-cli3 与@vue/web-component-wrapper 我们可以很简单地创建使用vue的web components。 web-component-wrapper
-
angular 支持转化成自定义组件 angular使用自定义元素
作者:doreenChenD
