要说最近几年来,前端开发最火的一个趋势或最火的前端开发框架是什么,第一想到的是,组件及推崇组件化开发的React框架。本文将介绍Web Components规范并就组件的几大特性进行讨论。javascript
在开启本篇的阅读以前,先问一个问题,组件是什么?html
组件,是数据和方法的一个封装,其定义了一个可重用的软件元素的功能,展现和使用,一般表现为一个或一组可重用的元素。前端
组件的特性是什么?一般能够总结为如下几点:java
组件化,给前端开发带来了极大的效率提高,是近几年以来web开发发展的趋势,各类组件化的用户界面库,框架也层出不穷,如,React,Vue,Ionic等,这些框架关于组件化都有各自的实现,推崇理念,与编程规范,各大框架的支持者之间的争论也是向来不断,而若想在不一样框架间切换,成本仍是挺高的,由于毕竟谁都但愿本身能占主流,占据绝对优点地位,就像当前IE与网景浏览器之争,延续到如今,各种浏览器标准兼容差别万千,近年来w3c不断在为web标准规范作努力,Web Components就是推出的关于组件化的一个标准,但愿它能将组件化更好的带进web开发,同时尽可能保证标准规范,开发者能够更好的关注于开发,而不是框架选择与争论之上。git
Web Components将一系列特性加入HTML和DOM规范,使得开发者能够自由建立在web应用或文档可重用的元素或部件,其由四部分组成:github
<template>
元素内声明。自定义元素支持开发者定义一类新HTML元素,声明其行为和样式,自定义元素分两类:web
支持建立自定义元素,Web Components比较好的实现了组件开发的可拓展性。ajax
为了建立一个自定义标签元素,咱们须要继承HTMLELement类, 如在不少页面咱们常常会有一键回到页面顶部功能,咱们建立一个返回顶部的组件:编程
class GoTop extends HTMLElement {
constructor() {
super();
}
}
customElements.define('go-top', GoTop);复制代码
在须要使用该组件的页面只需像使用正常HTML元素同样:浏览器
<go-top>Top</go-top>复制代码
固然,该元素的一切样式,行为,事件监听,默认行为均须要开发者自行定义,没法期待它有像<button>
同样的默认行为详细参考建立自定义标签元素。
不少时候咱们并不须要彻底建立一个新元素,而只是须要在某些内置元素基础上进行拓展,建立自定义内置元素,须要继承该类元素类,如HTMLButtonElement
或HTMLDivElement
:
class MenuButton extends HTMLButtonElement {
...
}
customElements.define('menu-button', MenuButton);复制代码
使用也很简单,和内置元素同样的语法;不一样的是,在须要使用自定义内置元素时,为内置元素添加is
特性,该特性值对应建立的自定义内置元素名称:
<button is="menu-button">menu</button>复制代码
该元素默认行为继承自<button>
元素,可是咱们能够为其设置拓展功能或性质。
经过上面实例可知,自定义标签元素与内置元素主要表如今两点不一样:
DOM,即文档对象模型,是HTML文档的一个结构表示,以树形结构表示一个文档,文档中元素间关系按照父子,兄弟关系排列;DOM规范提供一系列API支持咱们操做文档节点,即一般所说的DOM API。
前面提到Web Components指封装DOM和样式,以组件的形式在文档中使用,而不一样于JavaScript中函数会造成一个单独做用域,文档DOM树的层次结构中是不存在局部做用域概念的,也就是说文档内全部定义的样式都对整个文档产生影响,文档中的样式也会影响组件内的声明样式,而不限定于元素所处位置,这样显然极大阻碍了组件的独立性和可重用性,是必需要解决的问题,不过不用担忧,这都已经解决了,解决方案就是下文介绍的attachShadow()
方法。
影子DOM API提供了attachShadow()
方法,建立一个影子DOM,支持将封装的内容或组件做为一个独立DOM子树附加进一个HTML文档,组件内与外部隔离,样式互不影响,这也印证了组件开发的封装性需求。
要建立一个影子DOM,很简单,使用attachShadow()
方法便可,而须要注意的是全部影子DOM必须和一个文档中存在的元素(HTML内置元素或自定义元素)绑定,才能使用:
var frag = document.createElement('div');
var shadowRoot = frag.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<p>Shadow DOM Content</p>';复制代码
上文使用attachShadow()
方法建立的元素就是一个影子DOM,而其子内容就构成一棵影子树(shadow tree),而和影子DOM绑定,也就是包含该树的文档内元素一般称为影子主体(shadow host)。
如上,当一个元素(即影子主体)内存在影子DOM,浏览器默认只会渲染该影子DOM的影子树,而不渲染影子主体的其余子内容,如,现有某元素<div class="menus">
,在文档中使用以下:
<div class="menus">
<h2>Menus</h2>
</div>复制代码
给该元素绑定影子DOM:
var menus = document.querySelector('.menus');
var shadowRoot = menus.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<ul>\ <li>Home</li>\ <li>About</li>\ </ul>';复制代码
其影子树内容为:
<ul>
<li>Home</li>
<li>About</li>
</ul>复制代码
最后渲染结果以下:
<div class="menus">
<ul> <li>Home</li> <li>About</li> </ul>
</div>复制代码
你好发现影子主体本来的子元素内容没有被渲染,那么是否是没办法了?固然不是,若是要保存子内容,须要使用<slot>
槽位元素,至关于作一个占位符,只须要把前文影子主体内容修改成以下:
<div class="menus">
<h2>Menus</h2>
<slot></slot>
</div>复制代码
渲染结果以下, 一切符合需求:
<div class="menus">
<h2>Menus</h2>
<ul>
<li>Home</li>
<li>About</li>
</ul>
</div>复制代码
上文显示的是只有一个槽位的实例,假如须要有多个分组怎么办呢?Web Components也有解决方案,那就是使用命名槽,即给槽位添加name
属性,依然使用如上实例,修改影子主体内容:
<div class="menus">
<slot></slot>
<slot name="top"></slot>
<slot name="right"></slot>
</div>复制代码
假如影子树内容以下:
<h2>Menus</h2>
<ul slot="top">
<li>Home</li>
<li>About</li>
</ul>
<ul slot="right">
<li>Home</li>
<li>Top</li>
</ul>复制代码
渲染结果以下:
<div class="menus">
<h2>Menus</h2>
<ul>
<li>Home</li>
<li>About</li>
</ul>
<ul>
<li>Home</li>
<li>Top</li>
</ul>
</div>复制代码
如上,能够发现拥有name
属性的槽位由对应slot
属性值相同的影子子树替换,而剩下的内容默认替换空名槽位,若不存在空名槽位,则剩余内容将被抛弃。
前文已经提到,Web Components定义的组件内的样式与外部环境的样式是互不影响的,那么如何为组件设置样式呢,依然使用<style>
标签:
<head>
<style>
.top {margin-top: 30px;}
</style>
</head>
...
<div class="top">
...
</div>
...
<div class="menus">
#shadow-root
<style>
.top {margin-top: 10px;}
</style>
<div class="top">
...
</div>
</div>复制代码
如上实例,在组件内部top
类元素margin-top
值为10px,而外部top
类元素margin-top
值为30px,二者是独立的。
关于影子DOM树的渲染,其方式与web文档DOM树的渲染方式并没有区别,均由浏览器渲染引擎进行渲染,须要注意的是,影子树的DOM渲染过程和文档DOM树的渲染是独立分别进行的。
如何在HTML文档中引入另外一个web文档或web组件呢?像JSP或PHP语言都对HTML语法进行了拓展,咱们可使用诸如<include>
标签直接引入另外一个文档,然而在这以前,原生HTML规范并不支持直接引入另外一文档,一般都得经过ajax请求另外一文档内容,而后经过JavaScript使用DOM API将内容插入,对于组件化开发和使用,这样显然不是咱们指望的结果,这与组件的易用性是背离的,因此,HTML imports定义了如何在文档内引入和重用另外一文档。
在文档内直接引入外链资源的文档或web组件,语法以下,使用<link>
标签:
<link rel="import" href="components.html">复制代码
假如在components.html中定义了got-top
自定义元素,则在本文档内能够直接使用:
<go-top>GoTop</go-top>复制代码
如上,仅仅将<link>
标签的rel
属性设置成import便可,另外值得注意的是:为了不重复执行引入文档内的脚本,对于已加载文档,import方式将跳过其加载和执行过程。
为了更友好的处理组件模板,Web Components规范,支持<template>
模板标签,HTML模板定义了使用<template>
标签声明能够经过脚本操做插入文档的HTML模板片断:
<template id="menusTemplate">
<ul> <li>Home</li> <li>About</li> </ul>
</template>复制代码
使用脚本操做,该元素content
属性可访问模板内容:
var menusTemplate = document.querySelector('#menusTemplate');
var frag = document.importNode(menusTemplate.content, true);
document.querySelector('.menus').appendChild(frag);复制代码
<template>
标签本质上与其余HTML内置标签同样,可使用DOM API进行操做,可是须要明白,在将模板激活(生成DOM或插入文档)前:
<template>
标签内的内容不会被渲染;对于Web Components规范的兼容性,目前仍是须要使用webcomponentsjs polyfills的方式支持开发,总的来讲,目前Safari 10, Google Chrome (53)兼容的更好;虽然兼容性并很差,还在推动过程当中,可是对其进行学习仍是颇有必要的。
推荐几个常见的Web组件类库: