什么是Web Components?

Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的web应用中使用它们。

组件是前端的发展方向,现在流行的 React 和 Vue 都是组件框架。

谷歌公司由于掌握了 Chrome 浏览器,一直在推动浏览器的原生组件,即 Web Components API。相比第三方框架,原生组件简单直接,符合直觉,不用加载任何外部模块,代码量小。目前,它还在不断发展,但已经可用于生产环境。

Web Components 组成三要素

Web Components由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。

  • Custom elements(自定义元素):一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。

  • Shadow DOM(影子DOM):一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。

  • HTML templates(HTML模板): [<template>](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/template)[<slot>](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/slot) 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。template 可以简化生成 dom 元素的操作,我们不再需要 createElement 每一个节点。slot 则和 Vue 里面的 slot 类似,只是使用名称不太一样。

实现web component的基本方法通常如下所示:

  1. 创建一个类或函数来指定web组件的功能,如果使用类,请使用 ECMAScript 2015 的类语法(参阅获取更多信息)。
  2. 使用 [CustomElementRegistry.define()](https://developer.mozilla.org/zh-CN/docs/Web/API/CustomElementRegistry/define) 方法注册您的新自定义元素 ,并向其传递要定义的元素名称、指定元素功能的类、以及可选的其所继承自的元素。
  3. 如果需要的话,使用[Element.attachShadow()](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/attachShadow) 方法将一个shadow DOM附加到自定义元素上。使用通常的DOM方法向shadow DOM中添加子元素、事件监听器等等。
  4. 如果需要的话,使用 [<template>](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/template)[<slot>](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/slot) 定义一个HTML模板。再次使用常规DOM方法克隆模板并将其附加到您的shadow DOM中。
  5. 在页面任何您喜欢的位置使用自定义元素,就像使用常规HTML元素那样。

虽然 WebComponents 有三个要素,但却不是缺一不可的,WebComponents 借助 shadow dom 来实现样式隔离,借助 templates 来简化标签的操作。

customElements.define()自定义元素

window.customElements是CustomElementRegistry接口的实例。
image.png

Web Components 生命周期

connectedCallback:

当 WebComponents 第一次被挂在到 dom 上是触发的钩子,并且只会触发一次。类似 Vue 中的 mounted React 中的 useEffect(() => {}, []),componentDidMount。

disconnectedCallback

当自定义元素与文档 DOM 断开连接时被调用。

adoptedCallback:

当自定义元素被移动到新文档时被调用。

attributeChangedCallback 属性变化

当自定义元素的被监听属性变化时被调用。上述例子中我们监听了 type 的变化,使 Button 组件呈现不同状态。

Web Components 相关伪类

与自定义元素特别相关的伪类:

  • :defined: 匹配任何已定义的元素,包括内置元素和使用CustomElementRegistry.define()定义的自定义元素。
  • :host: 选择 shadow DOM的 shadow host ,内容是它内部使用的 CSS( containing the CSS it is used inside )。
  • :host(): 选择 shadow DOM的 shadow host ,内容是它内部使用的 CSS (这样您可以从 shadow DOM 内部选择自定义元素)— 但只匹配给定方法的选择器的 shadow host 元素。
  • :host-context()): 选择 shadow DOM的 shadow host ,内容是它内部使用的 CSS (这样您可以从 shadow DOM 内部选择自定义元素)— 但只匹配给定方法的选择器匹配元素的子 shadow host 元素。

    组件示例

  1. <cu-button type="warning">
  2. <span slot="btnText">
  3. 按钮
  4. </span>
  5. </cu-button>
  6. <template id="cuBtn">
  7. <style>
  8. .cu-button {
  9. display: inline-block;
  10. padding: 4px 20px;
  11. font-size: 14px;
  12. line-height: 1.5715;
  13. font-weight: 400;
  14. border: 1px solid #1890ff;
  15. border-radius: 2px;
  16. background-color: #1890ff;
  17. color: #fff;
  18. box-shadow: 0 2px #00000004;
  19. }
  20. .cu-button-warning {
  21. border: 1px solid #faad14;
  22. background-color: #faad14;
  23. }
  24. .cu-button-danger {
  25. border: 1px solid #ff4d4f;
  26. background-color: #ff4d4f;
  27. }
  28. </style>
  29. <div class="cu-button">
  30. <slot name="btnText"></slot>
  31. </div>
  32. </template>
  33. <script>
  34. const template = document.getElementById("cuBtn");
  35. class CuButton extends HTMLElement {
  36. constructor() {
  37. // 总是在构造函数中首先调用 super
  38. super()
  39. this._type = {
  40. primary: 'cu-button',
  41. warning: 'cu-button-warning',
  42. danger: 'cu-button-danger',
  43. }
  44. // 创建一个影子根元素 shadow dom
  45. const shadow = this.attachShadow({
  46. mode: 'open'
  47. })
  48. const type = this
  49. const content = template.content.cloneNode(true) // 克隆一份 防止重复使用 污染
  50. // 把响应式数据挂到 this
  51. this._btn = content.querySelector('.cu-button')
  52. this._btn.className += ` ${this._type[type]}`
  53. shadow.appendChild(content)
  54. }
  55. static get observedAttributes() {
  56. return ['type']
  57. }
  58. attributeChangedCallback(name, oldValue, newValue) {
  59. this[name] = newValue;
  60. this.render();
  61. }
  62. render() {
  63. this._btn.className = `cu-button ${this._type[this.type]}`
  64. }
  65. }
  66. // 定义新元素
  67. window.customElements.define('cu-button', CuButton)
  68. </script>

在这个例子用我们使用了 slot 传入了俩个标签之间的内容,如果我们想要不使用 slot 传入标签之间的内容,还可以通过innerHTML 拿到自定义组件之间的内容,然后把这段内容插入到对应节点即可。

参考

Web Components 入门实例教程

如何基于 WebComponents 封装 UI 组件库

使用::part伪元素改变Shadow DOM的CSS样式

https://www.zhangxinxu.com/wordpress/2021/02/css-part-shadow-dom/

Web Components中引入外部CSS的3种方法

https://www.zhangxinxu.com/wordpress/2021/02/web-components-import-css/