Web Components
CustomElement v1规范发布之后, 在不使用框架的情况下创建Web组件变得更容易.
如需直接编写Web Components, 请参考:
https://zh.javascript.info/web-components
https://developers.google.com/web/fundamentals/web-components/customelements
https://zh.javascript.info/web-components
https://developers.google.com/web/fundamentals/web-components/customelements
用户使用Widget时只需导入一段打包好的JavaScript代码(或者ESM), 然后在对应的HTML位置插入组件即可.
WC提供了一种与框架无关的组件封装能力, 这是现存的MVVM框架无法做到的.
在用WC模仿Angular或Vue这类基于模板引擎的框架时, 可能会按照以下方式编写HTML, 可读性不强:
<my-app> <my-nav></my-nav> <logic-if condition="$some_condition"> <template slot="then"> <my-article></my-article> </template> <template slot="else"> <my-article></my-article> </template> </logic-if></my-app>
由于WC在技术演进方面的持续失败, 它似乎永远不可能取代MVVM框架的位置:
MVVM框架在功能, 开发人员体验, 迭代速度, 生态环境上都远远强于WC.
MVVM框架在功能, 开发人员体验, 迭代速度, 生态环境上都远远强于WC.
Web Components尤其缺乏像React一样的高阶组件功能, 使得代码重用成为问题:
- Custom Elements必须在注册之后才能被重用.
渲染时, 需要提供被包装组件的名称, 而被包装组件此时可能只是一个临时组件, 既没有注册也不应该注册. - DOM里的HTMLElement是不能通过new运算符实例化的, Custom Elements规范也缺乏对渲染方法的定义.
这两点使得通用的高阶组件在Web Components里难以实现, 因为至少需要约定一个用于渲染的render方法.
此外, 由于Web Components的组件承载了太多功能, 组件之间的嵌套关系很容易把问题变得很复杂.
Shadow DOM是Web Components技术栈下的一项 可选功能,
Shadow DOM的存在使Web Components与现有的其他技术有了决定性不同.
Shadow DOM的存在使Web Components与现有的其他技术有了决定性不同.
遗憾的是, Shadow DOM这项技术本身不是很有意义:
Web开发并不存在非得引入Shadow DOM才能解决的问题, 这项技术很大程度上是在解决不存在的问题.
Web开发并不存在非得引入Shadow DOM才能解决的问题, 这项技术很大程度上是在解决不存在的问题.
目前看来, Shadow DOM的用途如下:
- 阻断脚本对组件内部DOM的访问.
我想不出除阻止用户脚本操作DOM以外的使用场景, 至少99.9%的网站不需要此项功能. - 阻断外部样式对组件内部DOM的影响.
由于中型以上的项目根本不会使用类名以外的方式为元素设置样式(基于选择器的方案是难以维护的, 注定会陷入仅追加样式的窘境),
这项功能显得非常多余.
对Shadow DOM节点树的称呼, 在很大程度上可以与Shadow DOM替换使用.
对Shadow Tree的根节点的称呼.
在此模式下, 可以让宿主环境的代码通过根元素的shadowRoot属性访问到Shadow DOM, 从而允许外部代码修改Shadow DOM里的元素属性.
在此模式下, 根元素的shadowRoot属性为null, 无法从外部访问(除非组件在创建时将attachShadow的返回值存储并暴露在外).
const originalAttachShadow = Element.prototype.attachShadowElement.prototype.attachShadow = function () { return originalAttachShadow.call(this, { mode: 'open' })}
Chrome提供了一个
在Firefox里, 可以直接通过访问元素的成员
chrome.dom.openOrClosedShadowRoot
方法用于访问ShadowRoot.在Firefox里, 可以直接通过访问元素的成员
element.openOrClosedShadowRoot
来访问ShadowRoot.Slot插槽将Light DOM的元素填充(实际表现更像是映射或者链接)
到自定义元素的Shadow DOM里(插槽位于Shadow DOM内, 填充的元素则来自Light DOM).
到自定义元素的Shadow DOM里(插槽位于Shadow DOM内, 填充的元素则来自Light DOM).
被填充的元素在Light DOM里获得的样式在填充后仍然有效.
由于Shadow DOM隔离了宿主, 所以无法在外部通过CSS选择器为内部元素设置样式.
Web Components的样式通常需要通过CSS变量或组件本身的属性来间接设置样式.
Web Components的样式通常需要通过CSS变量或组件本身的属性来间接设置样式.
Light DOM是与Shadow DOM相对的概念, 具体指代具有以下代码的组件:
// LitElement LightDOMcreateRenderRoot() { return this // 将内部元素暴露到文档级别, 而不是ShadowRoot里}// 对于一般CustomElement用例, 只需要用this替代this.shadowRoot
通过将host直接暴露为Light DOM, 从而支持通过CSS选择器为自定义元素设置样式.
来自Light DOM的样式会优先于Shadow DOM里通过
来自Light DOM的样式会优先于Shadow DOM里通过
:host
伪元素设置的样式.该方案的CSS选择器受到Shadow DOM的严重限制.
草案在Shadow Host上定义了一种
::part()
伪元素, 允许在外部通过CSS选择器设置内部元素的样式.::part()
无法通过伪元素继续匹配该元素的后代, 即 不能穿透多层Shadow Tree.当Web组件内部嵌套其他Web组件时, 无法设置深层Web组件的样式.
以上问题进而衍生出一个新的属性exportparts, 从而允许导出深层的Web组件的part.
带有重命名的导出:
<style> component::part(textspan) { color: red; }</style><!-- Shadow Tree in component --><!-- 用exportparts公开更深层的part, 多个导出用逗号隔开 --><inner-component exportparts="innerspan: textspan"></inner-component><!-- Shadow Tree in inner-component --><span part="innerspan">红色</span><span part="textspan">不是红色</span>
示例2: 直接导出
<style> component::part(innerspan) { color: red; }</style><!-- 用exportparts公开更深层的part, 多个导出用逗号隔开 --><inner-component exportparts="innerspan"><</inner-component><!-- Shadow Tree in inner-component --><span part="innerspan">红色</span>
该方案仍然存在一个问题, 即如何 导出深层元素的全部属性, 有提案建议引入通配符来解决问题.
与
::part()
基本一样, 但对Shadow Tree具有穿透效果, 因此可以避免定义大量exportparts.