发新帖

[前端资源] Web Components 是个什么样的东西【4】

大神级别 2016-10-14 164


CSS 相关

因为有 Shadow DOM 的存在,所以在 CSS 上又添加了很多相关的东西,其中一部分还是属于讨论中的草案,命名之类的可能会有变更,下边提及的内容主要来自文档:Shadow DOM in CSS scoping 1,很多部分在 chrome 是已经实现的了,有兴趣可以写 demo 试试。

因为 Shadow DOM 很大程度上是为了隔离样式作用域而诞生的,主文档中的样式规则不对 Shadow DOM 里的子文档生效,子文档中的样式规则也不影响外部文档。

但不可避免的,在某些场景下,我们需要外部可以控制 Shadow DOM 中样式,如提供一个组件给你,有时候你会希望可以自定义它内部的一些样式,同时,Shadow DOM 中的代码有时候可能需要能够控制其所属元素的样式,甚至,组件内部可以定义上边提到的通过 slot 传递进来的 HTML 的样式。所以呢,是的,CSS 选择器中添加了几个伪类,我们一一来看下它们有什么作用。

在阅读下边描述的时候,请留意一下选择器的代码是在什么位置的,Shadow DOM 内部还是外部。

:host 用于在 Shadow DOM 内部选择到其宿主元素,当它不是在 Shadow DOM 中使用时,便匹配不到任意元素。

在 Shadow DOM 中的 * 选择器是无法选择到其宿主元素的。

:host( <selector> ) 括号中是一个选择器,这个可以理解为是一个用于兼容在主文档和 Shadow DOM 中使用的方法,当这个选择器在 Shadow DOM 中时,会匹配到括号中选择器对应的宿主元素,如果不是,则匹配括号中选择器能够匹配到的元素。

文档中提供了一个例子:

<x-foo class="foo">
  <"shadow tree">
    <div class="foo">...</div>
  </></x-foo>

在这个 shadow tree 内部的样式代码中,会有这样的结果:

  • :host 匹配 <x-foo> 元素

  • x-foo 匹配不到元素

  • .foo 只匹配到 <div> 元素

  • .foo:host 匹配不到元素

  • :host(.foo) 匹配 <x-foo> 元素

:host-context( <selector> ),用于在 Shadow DOM 中来检测宿主元素的父级元素,如果宿主元素或者其祖先元素能够被括号中的选择器匹配到的话,那么这个伪类选择器便匹配到这个 Shadow DOM 的宿主元素。个人理解是用于在宿主元素外部元素满足一定的条件时添加样式。

::shadow 这个伪类用于在 Shadow DOM 外部匹配其内部的元素,而 /deep/ 这个标识也有同样的作用,我们来看一个例子:

<x-foo>
   <"shadow tree">
     <div>
       <span id="not-top">...</span>
     </div>
     <span id="top">...</span>
   </>
 </x-foo>

对于上述这一段代码的 HTML 结构,在 Shadow DOM 外部的样式代码中,会是这样的:

  • x-foo::shadow > span 可以匹配到 #top 元素

  • #top 匹配不到元素

  • x-foo /deep/ span 可以匹配到 #not-top 和 #top 元素

/deep/ 这个标识的作用和我们的 > 选择器有点类似,只不过它是匹配其对应的 Shadow DOM 内部的,这个标识可能还会变化,例如改成 >> 或者 >>> 之类的,个人感觉, >> 会更舒服。

最后一个,用于在 Shadow DOM 内部调整 slot 的样式,在我查阅的这个文档中,暂时是以 chrome 实现的为准,使用 ::content 伪类,不排除有更新为 ::slot 的可能性。我们看一个例子来了解一下,就算名称调整了也是差不多的用法:

<x-foo>
  <div id="one" class="foo">...</div>
  <div id="two">...</div>
  <div id="three" class="foo">
    <div id="four">...</div>
  </div>
  <"shadow tree">
    <div id="five">...</div>
    <div id="six">...</div>
    <content select=".foo"></content>
  </"shadow tree"></x-foo>

在 Shadow DOM 内部的样式代码中,::content div 可以匹配到 #one#three 和 #four,留意一下 #two 为什么没被匹配到,因为它没有被 content 元素选中,即不会进行引用。如果更换成 slot 的 name 引用的方式亦是同理。

层叠规则,按照这个文档的说法,对于两个优先级别一样的 CSS 声明,没有带 !important 的,在 Shadow DOM 外部声明的优先级高于在 Shadow DOM 内部的,而带有 !important 的,则相反。个人认为,这是提供给外部一定的控制能力,同时让内部可以限制一定的影响范围。

继承方面相对简单,在 Shadow DOM 内部的顶级元素样式从宿主元素继承而来。

至此,Web Components 四个部分介绍结束了,其中有一些细节,浏览器实现细节,还有使用上的部分细节,是没有提及的,因为详细记录的话,还会有很多东西,内容很多。当使用过程中有疑问时可以再次查阅标准文档,有机会的话会再完善这个文章。下一部分会把这四个内容组合起来,整体看下 Web Components 是怎么使用的。

Web Components

Web Components 总的来说是提供一整套完善的封装机制来把 Web 组件化这个东西标准化,每个框架实现的组件都统一标准地进行输入输出,这样可以更好推动组件的复用。结合上边各个部分的内容,我们整合一起来看下应该怎么使用这个标准来实现我们的组件:

<!-- components/header.html --><template id=""><style>::content li {  display: inline-block;  padding: 20px 10px;}</style><content select="ul"></content></template><script>(function() {  const element = Object.create(HTMLInputElement.prototype)  const template = document.currentScript.ownerDocument.querySelector('template')

  element.createdCallback = function() {    const shadowRoot = this.createShadowRoot()    const clone = document.importNode(template.content, true)
    shadowRoot.appendChild(clone)    this.addEventListener('click', function(event) {      console.log(event.target.textContent)
    })
  }  document.registerElement('test-header', { prototype: element })
})()</script>

这是一个简单的组件的例子,用于定义一个 test-header,并且给传递进来的子元素 li 添加了一些组件内部的样式,同时给组件绑定了一个点击事件,来打印点击目标的文本内容。

看下如何在一个 HTML 文件中引入并且使用一个组件:

<!-- index.html --><!DOCTYPE html><html>
  <head>
    <meta charset="utf-8">
    <title></title>

    <link rel="import" href="components/header.html">
  </head>
  <body>
    <test-header>
      <ul>
        <li>Home</li>
        <li>About</li>
      </ul>
    </test-header>
  </body></html>

一个 import 的 <link> 把组件的 HTML 文件引用进来,这样会执行组件中的脚本,来注册一个 test-header 元素,这样子我们便可以在主文档中使用这个元素的标签。

上边的例子是可以在 chrome 正常运行的。

所以,根据上边简单的例子可以看出,各个部分的内容是有机结合在一起,Custom Elements 提供了自定义元素和标签的能力,template 提供组件模板,import 提供了在 HTML 中合理引入组件的方式,而 Shadow DOM 则处理组件间代码隔离的问题。

不得不承认,Web Components 标准的提出解决了一些问题,必须交由浏览器去处理的是 Shadow DOM,在没有 Shadow DOM 的浏览器上实现代码隔离的方式多多少少有缺陷。个人我觉得组件化的各个 API 不够简洁易用,依旧有 getElementById 这些的味道,但是交由各个类库去简化也可以接受,而 import 功能上没问题,但是加载多个组件时性能问题还是值得商榷,标准可能需要在这个方面提供更多给浏览器的指引,例如是否有可能提供一种单一请求加载多个组件 HTML 的方式等。

在现在的移动化趋势中,Web Components 不仅仅是 Web 端的问题,越来越多的开发者期望以 Web 的方式去实现移动应用,而多端复用的实现渐渐是以组件的形式铺开,例如 React Native 和 Weex。所以 Web Components 的标准可能会影响到多端开发 Web 化的一个模式和发展。

最后,再啰嗦一句,Web Components 个人觉得还是未来发展趋势,所以才有了这个文章。


最新回复 (0)
登录后再回复
返回