Web Component 是什么?

简介

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

-- Web Components | MDN[1]

关于它的其它介绍:

  • 组件是前端的发展方向,现在流行的 React 和 Vue 都是组件框架。而 Web Component 相比第三方框架,原生组件简单直接,符合直觉,不用加载任何外部模块,代码量小。目前,已经发展的比较成熟,并用于生产环境。

  • 组件化开发使得我们可以将页面切割成模块便于封装和开发,而 Web Component 在此基础上,可以将每个组件渲染在独立的 DOM 树中,天然支持模块间样式和逻辑的隔离。

特性

这里会对 Web Component 的相关属性做一个简单介绍。

Web Component 特性完整代码:

https://codesandbox.io/s/snowy-darkness-jmdip7

Custom Elements

一组 Javascript API,允许您定义 Custom Elements 及其行为,然后在您的用户界面中按照需要使用它们。

window.customElements
// custom button
class CustomButton extends HTMLElement {constructor() {super();const button = document.createElement("button");button.innerText = this.getAttribute("name") || "custom button";button.disabled = true;this.appendChild(button);}
}window.customElements.define("custom-button", CustomButton);
  • window.customElements.get

该方法用来获取自定义组件的构造函数,接收一个参数,即声明的自定义组件的 name,返回构造函数。

const getCustomConstructorBefore = customElements.get('custom-button');
// getCustomConstructorBefore before:  undefined
console.log('getCustomConstructorBefore before: ', getCustomConstructorBefore);
customElements.define("custom-button", CustomButton);
const getCustomConstructorAfter = customElements.get('custom-button');
// getCustomConstructorAfter after:  ƒ CustomButton() {}
console.log('getCustomConstructorAfter after: ', getCustomConstructorAfter);
  • window.customElements.upgrade

customElements upgrade() 方法升级节点子树中文档的所有包含 shadow dom 的(亲测,可以不包含 shadow dom)自定义元素,甚至在它们连接到主文档之前。接收一个参数,即一个自定义组件节点,无返回值。

// 先创建自定义标签
const el = document.createElement("spider-man");class SpiderMan extends HTMLElement {}
// 后声明构造函数
customElements.define("spider-man", SpiderMan);// false
console.log(el instanceof SpiderMan);
// 建立自定义标签与构造函数之间的绑定关系
customElements.upgrade(el);
// true
console.log(el instanceof SpiderMan);
  • window.customElements.whenDefined

该方法用来检测并提供自定义组件被定义声明完毕的时机,接收一个参数,即自定义元素的 name,返回值是一个 Promise,若提供的 name 无效,则触发 Promise 的 catch,可以用来判断是否定义了同名的 Custom Element。

Custom Element 重复定义的报错:

我们可以用这个方法来捕获重复定义的报错,最推荐 define 之前先 get 一下~

customElements.whenDefined('custom-button').then(() => {customElements.define('custom-button', CustomButton);
}).catch((err) => {console.log(err, 'err-----')
});

捕获的报错信息:

生命周期

Custom Elements提供了一些生命周期函数,使得我们能在自定义元素在 DOM 中的行为变化后执行相关逻辑。

  • ConnectedCallback:当自定义元素第一次被连接到文档 DOM 时调用(类似组件 Mounted);

  • attributeChangedCallback:当自定义元素的一个属性被增加、移除或更改时被调用,需要配合静态方法 observedAttributes 来使用,设置只有注册在 observedAttributes 中的属性才被监听;

  • disconnectedCallback:当自定元素与文档 DOM 断开连接时调用(类似 Unmount);

  • adoptedCallback:当自定义元素被移动到新文档时被调用;

示例代码:

class CustomButton extends HTMLElement {constructor() {super();const button = document.createElement("button");button.innerText = "custom button";button.addEventListener("click", this.changeAttribute.bind(this));const textSpan = document.createElement("span");textSpan.innerText = this.getAttribute("text") || "default";textSpan.className = "info";this.appendChild(button);this.appendChild(textSpan);}connectedCallback() {console.log("自定义button被连接到DOM啦~");}// observedAttributes,定义特定属性变化时,触发attributeChangedCallback函数// 未定义的属性改变,则不会触发回调static get observedAttributes() {return ["style", "text"];}// 与observedAttributes结合使用attributeChangedCallback(name, oldValue, newValue) {if (name === "text" &&oldValue !== newValue &&this.querySelector(".info")) {this.querySelector(".info").innerText = newValue;}}changeAttribute() {this.setAttribute("text", "sfsdfd");}disconnectedCallback() {console.log("自定义button与DOM断开连接啦~");}adoptedCallback() {// 创建一个iframe的document,并移动进去console.log("自定义button移动到新文档啦~");}clickToRemove() {// 从DOM中移除自身this.parentNode.removeChild(this);}
}window.customElements.define("custom-button", CustomButton);

Shadow DOM

一组 Javascript API,可以将封装的“Shadow DOM”树附加到元素(与主文档分开呈现),并控制其关联的功能。通过这种方式,您可以拥有一个天然的沙箱环境,保持元素的功能私有,实现样式和脚本的隔离,不用担心与文档的其他部分发生冲突。

示例代码:

class CustomShadowDOM extends HTMLElement {constructor() {super();// 创建一个shadowDOMconst shadow = this.attachShadow({ mode: "open" });const info = document.createElement("span");const style = document.createElement("style");const css = "span { color: red; }";style.type = "text/css";style.appendChild(document.createTextNode(css));info.setAttribute("class", "info");info.textContent = this.getAttribute("text") || "default";// shadow可以创建一个不受外部影响,切拥有内部js运行逻辑,拥有独立的// css的自定义元素(也就是web component),shadow.appendChild(style);shadow.appendChild(info);}
}window.customElements.define("custom-shadow-dom", CustomShadowDOM);