是 Web 标准中的一项功能,它允许开发者自定义新的 HTML 元素,开发者可以使用 JavaScript 和 DOM API,使新元素具有自定义的行为和功能
4.13.1.1 Creating an autonomous custom element
This section is non-normative.
For the purposes of illustrating how to create an [autonomous custom element](#autonomous custom element "#autonomous-custom-element"), let's define a custom element that encapsulates rendering a small icon for a country flag. Our goal is to be able to use it like so:
ini复制代码
<flag-icon country="nl"></flag-icon>
To do this, we first declare a class for the custom element, extending HTMLElement:
javascript复制代码
class FlagIcon extends HTMLElement {
constructor() {
super();
this._countryCode = null;
}
static observedAttributes = ["country"];
attributeChangedCallback(name, oldValue, newValue) {
// name will always be "country" due to observedAttributes
this._countryCode = newValue;
this._updateRendering();
}
connectedCallback() {
this._updateRendering();
}
get country() {
return this._countryCode;
}
set country(v) {
this.setAttribute("country", v);
}
_updateRendering() {
...
}
}
We then need to use this class to define the element:
除了继承 HTMLElement,也可以继承其既有子类,并在使用是采用原生标签(被继承类) + is 语法,如:
scala复制代码
// Create a class for the element
class WordCount extends HTMLParagraphElement {
constructor() {
// Always call super first in constructor
super();
// Constructor contents omitted for brevity
// ...
}
}
// Define the new element
customElements.define("word-count", WordCount, { extends: "p" });
鉴于这个原因,Web components 的一个重要属性就是封装------可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离。其中,Shadow DOM 接口是关键所在,它可以将一个隐藏的、独立的 DOM 附加到一个元素上
Shadow DOM 是 DOM nodes 的附属树。这种 Shadow DOM 子树可以与某宿主元素相关联,但并不作为该元素的普通子节点,而是会形成其自有的作用域;Shadow DOM 中的根及其子节点也不可见。
相比于以前为了实现封装而只能使用 <iframe> 实现的情况,Shadow DOM 无疑是一种更优雅的创建隔离 DOM 树的方法。
Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中------它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样。
这里,有一些 Shadow DOM 特有的术语需要我们了解:
Shadow host:一个常规 DOM 节点,Shadow DOM 会被附加到这个节点上。
Shadow tree:Shadow DOM 内部的 DOM 树。
Shadow boundary:Shadow DOM 结束的地方,也是常规 DOM 开始的地方。
Shadow root: Shadow tree 的根节点。
你可以使用同样的方式来操作 Shadow DOM,就和操作常规 DOM 一样------例如添加子节点、设置属性,以及为节点添加自己的样式(例如通过 element.style 属性),或者为整个 Shadow DOM 添加样式(例如在 <style> 元素内添加样式)。不同的是,Shadow DOM 内部的元素始终不会影响到它外部的元素(除了 :focus-within),这为封装提供了便利。
注意,不管从哪个方面来看,Shadow DOM 都不是一个新事物------在过去的很长一段时间里,浏览器用它来封装一些元素的内部结构。以一个有着默认播放控制按钮的 <video> 元素为例。你所能看到的只是一个 <video> 标签,实际上,在它的 Shadow DOM 中,包含了一系列的按钮和其他控制器。Shadow DOM 标准允许你为你自己的元素(custom element)维护一组 Shadow DOM。
如果你将一个 Shadow root 附加到一个 Custom element 上,并且将 mode 设置为 closed,那么就不可以从外部获取 Shadow DOM 了------myCustomElem.shadowRoot 将会返回 null。浏览器中的某些内置元素就是如此,例如<video>,包含了不可访问的 Shadow DOM。
如果你想将一个 Shadow DOM 附加到 custom element 上,可以在 custom element 的构造函数中添加如下实现(目前,这是 shadow DOM 最实用的用法):
ini复制代码
let shadow = this.attachShadow({ mode: "open" });
将 Shadow DOM 附加到一个元素之后,就可以使用 DOM APIs 对它进行操作,就和处理常规 DOM 一样。
ini复制代码
var para = document.createElement('p');
shadow.appendChild(para);
etc.
注意:
要使用 Chrome 调试器检查 Shadow DOM,需要选中调试器的 Preferences / Elmenets 下的 show user agent shadow DOM 框*;比如对于上文提到的 <video>,在打开该调试选项后,就能在元素面板中看到 <video> 下挂载的 shadow tree
一些比较旧的资料中会出现 attachShadow() 的前身 createShadowRoot(),语义基本相同;createShadowRoot()已经被废弃,它是在 Shadow DOM v0 规范中引入的。Shadow DOM 的最新版本是 v1,是 Web 标准的一部分。
The CSS scoping module defines the CSS scoping and encapsulation mechanisms, focusing on the Shadow DOMscoping mechanism.
根据 Shadow DOM 作用域机制,CSS scoping 模块定义了 CSS 作用域和封装机制
CSS styles are either global in scope or scoped to a shadow tree. Globally scoped styles apply to all the elements in the node tree that match the selector, including custom elements in that tree, but not to the shadow trees composing each custom element. Selectors and their associated style definitions don't bleed between scopes.
CSS 样式分为全局和 shadow tree 局部两种。全局样式应用于节点树中与选择器匹配的所有元素,包括该树中的自定义元素,但不应用于组成每个自定义元素的shadow tree。选择器及其关联的样式定义也不会在作用域之间流通。
Within the CSS of a shadow tree, selectors don't select elements outside the tree, either in the global scope or in other shadow trees. Each custom element has its own shadow tree, which contains all the components that make up the custom element (but not the custom element, or "host", itself).
If you render a tag with a dash, like , React will assume you want to render a custom HTML element. In React, rendering custom elements works differently from rendering built-in browser tags:
All custom element props are serialized to strings and are always set using attributes. Custom elements accept class rather than className, and for rather than htmlFor. If you render a built-in browser HTML element with an is attribute, it will also be treated as a custom element.
javascript复制代码
import React, { useState } from 'react';
import './alert.js';
export default function App() {
const [show, setShow] = useState(true);
return (
<div>
<button onClick={() => setShow(!show)}>toggle alert</button>
<x-alert hidden={show} status="success" closable oncloseChange={() => setShow(!show)}>
This is a Web Component in React
</x-alert>
</div>
);
}
而如果想将标准 react 组件包装为 web component,可以在 react 工程中直接结合 web component 原生语法、使用 React 完成节点渲染,并导出成独立组件。
Vue implements a content distribution API inspired by the Web Components spec draft, using the <slot> element to serve as distribution outlets for content.
-- vue2官方文档
源自 Vue 2.x 时代对 Web Components 的关注,Vue 3 更进一步,原生支持了将 Vue 3 组件导出为 Web Components: