前端组件化这件事情不知道耗费了多少工程师的心血,在WebComponents之前,HTML自身并不支持模块化或组件化,但组件化的需求是客观存在的。在此之前已经有无数的解决方案。各种模板有handlebars,jade,ejs等,css解决方案bootstrap,还有前端框架比如react和angular都在解决组件化的问题。但是所有的这些方案创建的组件都不能用在其他方案中,简直是蛋疼。
从近几年一些技术发展可以得到一个趋势:一些重要的问题一定会被浏览器原生支持,而一旦浏览器原生支持,与之功能重合的工具和框架就会死去:
<input type="date">
来了JQuery插件datapicker就死了 那么组件化这个问题这么蛋疼,会不会被原生解决?当然,一个原生的通用组件化标准正在制定中!
WebComponents 是一组w3c标准的集合,Google 主推。它的思想是让浏览器原生支持自定义标签,让HTML可以像模板引擎一样引入另一个html作为组件。它并不是一个框架,而是希望用户自定义的组件可以无缝的使用在其他任何框架中,WebComponents也没有绑死到任何一个框架。想象WebComponent的时代我们可以做这些事情:
然而当WebComponents到来,谁会死去?(我没有说React)
WebComponents 是一套标准集合,包含以下几个标准:
其中大多数spec的状态都是Editor's Draft,意味着还没有普及大众,还有可能修改。只有Template标准是WD,正在被执行的标准。
既然是这样还说个蛋?都不支持就是还不能用啊!
暂且别急,应用的事下文会提到。我们先分析标准。
http://w3c.github.io/webcomponents/spec/shadow/
Shadow DOM 标准存在的意义是封装DOM,其实Shadow DOM在我们身边都存在很长时间了。
假如你使用Chrome或其他支持Shadow DOM的浏览器,请看以下密码输入标签:
如果看不到可以使用任何一个密码输入标签,在里面输入密码,是不可见的。通过审查元素也看不到!那么问题来了,浏览器把数据存到了哪里呢? 审查元素:
马上让它现形! F12 调出开发者工具,点击右边的齿轮(设置)之后,在中间有个选项: Show user agent shadow DOM
把它勾上后再审查元素看看
惊喜的发现 <input>
标签并非世界尽头,就好像发现原子里面还有结构一样。基本元素内部的结构就是Shadow DOM。input标签内部居然有个div,在这个div的content里保存的才是真正的内容。其实不止input标签,还有其他一些标签也一样。
考虑为何Chrome使用Shadow DOM。原因只有1个:一颗DOM树已经不够用了。一方面希望开发者只关注业务,另一方面页面变得越来越复杂,不得不展现更多内容。Shadow DOM对于组件化的意义非常重要,它为上层展现一个简单的界面,隐藏丰富的细节。比如input 标签,在上层看来就是一个不可分的元素,没有人会在乎数据在标签内部是怎么传输的,事件是怎么传播的,完美的达到了封装的效果,这中封装的元素在react ,angular中使用自然是没有问题的,可以做通用的组件。
上文对于Shadow Dom有了感性的认识,下面我们深入到细节: 内容全部来自于 http://w3c.github.io/webcomponents/spec/shadow/ 和自己的理解。
最大的一个变化是:DOM 树形模型的扩展,以前是元素的树,现在是树的树(Tree Of Tree):
一张图展示了几个概念:
对应上面的 <input>
的例子可以很直观的理解: input 标签是 shadow host,而shadow root 就写在那里。
Shadow Dom 是四个规范中最难搞的一个,一方面你要在大部分API中隐藏Shadow DOM,另一方面你希望渲染的时候尽量简单,把DOM还当一整棵树来处理,把shadow DOM在渲染的时候当成DOM 树的一部分,这就是 Composed Tree。
显然,把Tree Of Tree 变成一棵树需要一定的转化算法。 不属于此文的范畴,我们只需要知道转化之前是什么样子,转化之后是什么样子就行。其实也没什么,就是把shadow DOM挂在父级标签下面就好了。
确实,到目前为止还是很简单的,但马上一个怪胎出现了--slot!
什么鬼?
一个图来说明slot在compose过程中的怪异行为
slot 在compose后并不存在,而是原本的位置上抢了shadow host 下的子元素作为自己的替身。其实slot本身才是替身,可以理解为占位符。
好了,理解了slot是占位符的本质,我们又问,这鬼是做什么用的?
为了host 和 shadow DOM之间传递数据,也为了方便维护,如果一个数据在host中和shadow中都存在,那么只维护宿主,在shadow中使用slot就行。就到这里,不再展开。
当前很多支持Shadow DOM的浏览器使用的是 content
标签,而不是 slot
,毕竟标准还没有定稿。
Shadow DOM的出现让事件的机制更加复杂。
一方面由于封装,一些事件不会传播到Shadow DOM之外。
另一方面,其他事件传播的路径遵循原始的DOM,而不是composed dom。也就是说shadow root和slot 也会参与到事件路径中。
还有一点需要注意的:封装的思想在事件中依然存在。在上层DOM看来的事件的target不能包含底层DOM的细节。所以,在不同层级的树中看到同一事件的target是不一致的。这就叫做 event retargeting 。
再举 <input>
的例子:在全局的dom中看到的click事件的target是 <input>
,而这个事件的起源可能是shadow DOM中的 <div id=editor>
,我们都知道事件向上'冒泡'传播,在shadow DOM 之内,这个事件的target是 <div>
,而事件传播出Shadow DOM,事件的target 变成了 <input>
!
再加上还有些标签有 relatedTaget 就更复杂了,不细说
值得注意的是,Shadow DOM的封装不仅对html起作用,对于CSS同样起作用,在Shadow DOM内定义的CSS作用于只在这个Shadow DOM之内,而不会影响其他DOM。
Shadow DOM的出现一定影响了某些接口(当然)。
包括一个新元素 ShadowRoot
扩展了Element,增加了 attachShadow
方法,用于在一个元素下面增加一个shadow DOM等。
下文一并处理吧!
http://w3c.github.io/webcomponents/spec/custom/ 已经有了Shadow DOM这样的利器,你不会认为就做个 input
这么简单吧!要想使用这把大杀器,还需要自定义元素(自定义标签)这个角色。
这个标准的目标是:让开发者自己定义功能完整的DOM元素。自定义元素的本质是让开发者教浏览器新的元素定义。因此这个标准的重点是提供一系列接口,让开发者教浏览器新的元素行为如何。
首先浏览器规范从一开始就允许非标准标签的出现,只不过这些标签的类别被当作 HTMLUnknownElement
。这一点很重要,否则如果浏览器遇到不认识的标签就报错,就无法做自定义标签来,AngularJS这类框架也就不会出现。
那么自定义标签都能干毛?
首先看,既然可以教浏览器新的标签,那么浏览器就一定要有一个数据结构保存已知标签的定义,这个东西就叫 registery
就翻译成注册器吧。一个document只能有1个文档。
一个DOM元素一定要有一个接口名比如 html
, body
, div
等,而自定义元素的接口名必须是两部分:命名空间和本地命名,两者必须以 -
分隔(好像一定是这个样子,没有为什么)
比如 <wilddog-list></wilddog-list>
命名空间是 wilddog
而本地命名是 list
教浏览器认识新标签只告诉一个名字显然是不够的。要教会它行为。有两种方式
那么这个接口被设计成来这个样子
partial interface Document { Function registerElement(DOMString type, optional ElementRegistrationOptions options); }; dictionary ElementRegistrationOptions { object? prototype = null; DOMString? extends = null; };
TBD
TBD
TBD