文档章节

Web Components简述

熊建刚
 熊建刚
发布于 2017/05/16 10:25
字数 3128
阅读 2170
收藏 69

前言

在开启本篇的阅读之前,先问一个问题,组件是什么?

组件,是数据和方法的一个封装,其定义了一个可重用的软件元素的功能,展示和使用,通常表现为一个或一组可重用的元素。

组件的特性是什么?通常可以总结为以下几点:

  1. 可拓展性:既然组件是针对某一特定功能或需求开发的,那它就必须易于开发和拓展;
  2. 封装性:组件作为一个独立整体供使用,应该是对内修改,对外封闭,只供使用,而不对使用环境产生副作用;
  3. 易用性:组件的目的是产生可重用的独立部件,那就必须提供一种简单快捷的方式供使用。

组件化,给前端开发带来了极大的效率提升,是近几年以来web开发发展的趋势,各种组件化的用户界面库,框架也层出不穷,如,React,Vue,Ionic等,这些框架关于组件化都有各自的实现,推崇理念,与编程规范,各大框架的支持者之间的争论也是向来不断,而若想在不同框架间切换,成本还是挺高的,因为毕竟谁都希望自己能占主流,占据绝对优势地位,就像当前IE与网景浏览器之争,延续到现在,各类浏览器标准兼容差异万千,近年来w3c不断在为web标准规范做努力,Web Components就是推出的关于组件化的一个标准,希望它能将组件化更好的带进web开发,同时尽量保证标准规范,开发者可以更好的关注于开发,而不是框架选择与争论之上。

Web Components特征

Web Components将一系列特性加入HTML和DOM规范,使得开发者可以自由创建在web应用或文档可重用的元素或部件,其由四部分组成:

  • 自定义元素(Custom Elements):定义新HTML元素的一系列API;
  • 影子DOM(Shadow DOM):组合对DOM和样式的封装;
  • HTML导入(HTML Imports):定义在文档中导入其他HTML文档的方式;
  • HTML模板(HTML Templates):HTML内的DOM模板,在<template>元素内声明。

自定义元素(Custom Elements)

自定义元素支持开发者定义一类新HTML元素,声明其行为和样式,自定义元素分两类:

  • 自定义标签元素(Autonomous custom elements):完全独立于原始HTML元素标签的新标签元素,其所有行为需要开发者定义;
  • 自定义内置元素(Customized built-in):基于HTML原始元素标签的自定义元素,以便于使用原始元素的特性,开发者只需要定义拓展行为;

支持创建自定义元素,Web Components比较好的实现了组件开发的可拓展性。

创建自定义标签元素

为了创建一个自定义标签元素,我们需要继承HTMLELement类, 如在很多页面我们经常会有一键回到页面顶部功能,我们创建一个返回顶部的组件:


	class GoTop extends HTMLElement {
		constructor() {
    		super();
  		}
	}
	customElements.define('go-top', GoTop);

在需要使用该组件的页面只需像使用正常HTML元素一样:


	<go-top>Top</go-top>

当然,该元素的一切样式,行为,事件监听,默认行为均需要开发者自行定义,无法期待它有像<button>一样的默认行为详细参考创建自定义标签元素

创建自定义内置元素

很多时候我们并不需要完全创建一个新元素,而只是需要在某些内置元素基础上进行拓展,创建自定义内置元素,需要继承该类元素类,如HTMLButtonElementHTMLDivElement


	class MenuButton extends HTMLButtonElement {
		...
	}
	customElements.define('menu-button', MenuButton);

使用也很简单,和内置元素一样的语法;不同的是,在需要使用自定义内置元素时,为内置元素添加is特性,该特性值对应创建的自定义内置元素名称:


	<button is="menu-button">menu</button>

该元素默认行为继承自<button>元素,但是我们可以为其设置拓展功能或性质。

对比

通过上面实例可知,自定义标签元素与内置元素主要表现在两点不同:

  • 标签:自定义标签元素是完全独立的一个新元素,新标签,而自定义内置标签,使用的依然是已有内置标签;
  • 行为与样式:自定义内置元素,继承内置元素的默认行为,样式及语义,可以进行拓展,而自定义标签元素,完全需要开发者定义相关声明。

影子DOM(Shadow DOM)

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>';

影子树(shadow tree)与影子主体(shadow host)

上文使用attachShadow()方法创建的元素就是一个影子DOM,而其子内容就构成一棵影子树(shadow tree),而和影子DOM绑定,也就是包含该树的文档内元素通常称为影子主体(shadow host)。

槽位(slot)

如上,当一个元素(即影子主体)内存在影子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>
命名槽(named slots)

上文显示的是只有一个槽位的实例,假如需要有多个分组怎么办呢?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引入(HTML Imports)

如何在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方式将跳过其加载和执行过程。

HTML模板(HTML Templates)

为了更友好的处理组件模板,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标签

<template>标签本质上与其他HTML内置标签一样,可以使用DOM API进行操作,但是需要明白,在将模板激活(生成DOM或插入文档)前:

  1. <template>标签内的内容不会被渲染;
  2. 标签内的图片,等媒体资源不会被加载;
  3. 标签不会出现在DOM树,审查元素看不到;

关于兼容性

对于Web Components规范的兼容性,目前还是需要使用webcomponentsjs polyfills的方式支持开发,总的来说,目前Safari 10, Google Chrome (53)兼容的更好;虽然兼容性并不好,还在推进过程中,但是对其进行学习还是很有必要的。

类库

推荐几个常见的Web组件类库:

  • Polymer: Google推出的web组件库,支持数据的单向和双向绑定,兼容性较好,跨浏览器性能也较好;
  • X-Tag: 微软推出的开源库,支持Web Components规范,兼容Web ComponentsAPI;
  • Slim.js:轻量级的web组件库,专注于帮助开发者更好的编写原生web组件,而不依赖于其他框架,但是也提供了良好的拓展性,开发者可以自由拓展。

参考

  1. Component
  2. Web Components Specifications
  3. Web Components WiKi
  4. Web Components MDN

欢迎访问我的个人博客 - 熊建刚的博客

© 著作权归作者所有

共有 人打赏支持
熊建刚
粉丝 64
博文 21
码字总数 88314
作品 0
海淀
程序员
加载中

评论(8)

熊建刚
熊建刚

引用来自“BruceWan”的评论

报错

回复@BruceWan : 是啊,网站前端代码报错了
熊建刚
熊建刚

引用来自“kernel64”的评论

加载不出来,评论也失败

回复@kernel64 : 我看所有博客都加载不了,他们前端代码报错了,欢迎访问我的个人博客http://blog.codingplayboy.com/2017/05/15/web-components/
BruceWan
BruceWan
报错
kernel64
kernel64
加载不出来,评论也失败
kernel64
kernel64
加载不出来
kernel64
kernel64
加载不出来
kernel64
kernel64
加载不出来
kernel64
kernel64
加载不出来
yii2搭建完美后台并实现rbac权限控制

作者:白狼 出处:http://www.manks.top/article/yii2framerbac_template 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律...

白狼栈
2016/03/30
554
2
使用CLI 3 创建发布Web Components

本文翻译自:codementor 翻译不当之处,欢迎指正交流 Web Components是web平台的未来吗?关于这一问题支持和反对的观点有很多。事实上浏览器对Web Components的支持正在逐渐形成,并有越来越...

RachelehcaR
09/13
0
0
AngularJS 和 Polymer 的角色,是否有竞争关系

AngularJS 2.0 一个关键的特性就是支持 Web Components。而 Google 的 Polymer 就是一个 Web Component 技术的实现,同时也是一个基于 Web Components 的框架。那么 AngularJS 和 Polymer 之...

oschina
2014/07/26
9.3K
18
WEB 组件规范--xspec

xspec 是一系列基于WEB COMPONENTS的组件规范,致力于借助规范的力量让组件发挥更大的价值。 什么是 X-SPEC 它是一系列 WEB组件的规范集合,基于最新的 WEB COMPONENTS, 从 WEB 组件的标签、...

叶秀兰
2014/07/30
441
0
React Native动态表单 新框架 新设计

简述 在脚本语言里,设计一个动态表单,确实比java要容易的多,下面介绍一个动态表单框架,在阅读了网络很多开源的动态表单设计源码之后,我们决定自己动手做一个,使用了ES6的特性,也解决了...

Pape
09/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

多线程

1. 多线程概念。并发和并行的概念。 多线程指的是一段时间内cpu同时执行多个线程。一个程序至少运行>=1个进程,进程就是运行中的程序,而一个进程至少运行>=1个线程,线程是操作系统能调度的...

鱼想吃肉
今天
0
0
HBase 表修复在线方式和离线方式

一、在线修复 1.1 使用检查命令 $ ./bin/hbase hbck 该命令可完整修复 HBase 元数据信息;存在有错误信息会进行输出; 也可以通过如下命令查看详细信息: $ ./bin/hbase hbck -details 1.2 ...

Ryan-瑞恩
今天
3
0
redis 系列二 -- 常用命令

1.基础命令 info ping quit save dbsize select flushdb flushall 2.键命令 2.1 set 直接赋值 set a a 2.2 get 取值 get a 2.3 exists 是否存在 exists a 2.4 expire 设置剩余时间 秒 expire......

imbiao
今天
2
0
php foreach

<?php// 数组的引用$a=array(1,2,3,4,5);foreach($a as $key=>&$value){$value=$value*2;}print_r($a);echo " $key -------------------$value\r\n";/** * ...

小张525
今天
3
0
12-利用思维导图梳理JavaSE-多线程

12-利用思维导图梳理JavaSE-多线程 主要内容 1.线程概念 2.线程开发 3.线程的状态 4.线程的同步和死锁 5.Java5.0并发库类 QQ/知识星球/个人WeChat/公众号二维码 本文为原创文章,如果对你有一...

飞鱼说编程
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部