关于 Web Components 的18个疑问~

家好,很高兴又见面了,我是"高级前端‬进阶‬",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。

最近看到了关于Web Components的18个疑问的文章,觉得比较有意思,特地分享给大家。主要内容包括:

当然,本文主要翻译了前面几个疑问,如果大家有兴趣可以在文末的参考资料中阅读全文。

前言

当第一次了解 Web Component 的时候,大多数前端开发者都会涌现出很多疑问,特别是关于可访问性的问题。这篇文章将带着大家一起聊聊关于 Web Component 的一切。

什么是 Web Component

Web Component 是一组 Web 平台 API,允许开发者构建自己的功能齐全的 DOM 元素,Web Component 基于四个核心概念:

自定义元素

自定义元素为 Web Component 奠定了基础,其允许开发者在 JavaScript 中定义自定义元素。

当然,如果不需要 API 来创建自定义元素,那么开发者可以在没有 JavaScript 的情况下创建自定义元素。

Luke Skywalker
star-wars {
  display: block;
}

可以像上面的代码一样创建一个未定义的自定义元素,但可以用它做的事情并不多。 优点是开发者可以通知解析器如何正确构造元素以及该类的元素应如何对更改和事件做出反应。

在以下示例中,创建一个 StarWars 类并定义一个名为 的自定义元素,并使用 StarWars 类作为构造函数。 这就是定义和升级(将其提升为正确的已定义元素)自定义元素所需的全部内容。

class StarWars extends HTMLElement {
  constructor() {
    super();
  }
}
customElements.define('star-wars', StarWars);

在该类中,可以在组件生命周期的不同阶段运行不同的代码。例如,可以在组件连接或断开连接或属性更改时执行某些操作。


每次 character 属性发生更改时,都会调用 Star Wars API 来获取有关所提供角色的信息。

class StarWars extends HTMLElement {
  static observedAttributes = ['character'];

  constructor() {
    super();
    this._character = null;
  }

  connectedCallback() {
    console.log('connected');
  }

  disconnectedCallback() {
    console.log('disconnected');
  }

  attributeChangedCallback(name, oldValue, newValue) {
    this._character = newValue;
    this._getCharacter();
  }

  async _getCharacter() {
    console.log(`New Character: ${this._character}`);
    const response = await fetch(
      `https://swapi.dev/api/people/?search=${this._character}`
    );
    const characters = await response.json();
    const character = characters.results[0];
    this.innerHTML = `
Name
${character.name}
Hair color
${character.hair_color}
Eye color
${character.eye_color}
`; } } customElements.define('star-wars', StarWars);

Shadow DOM

Shadow DOM API 允许将封装的“shadow”DOM 子树附加到自定义元素,该元素与主文档 DOM 分开渲染。

Shadow DOM 允许隔离 subtree 并将样式和脚本封装在组件内,以避免与文档的其余部分发生冲突。 在以下示例中,在构造函数中附加了一个 shadow。 然后创建一个 button,并且不直接将其附加到元素 (this),而是附加到新创建的影子 DOM (this.shadowRoot)。

class CustomButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    const button = document.createElement('button');
    button.textContent = 'Click me';

    this.shadowRoot.append(button);
  }
}

customElements.define('the-button', CustomButton);

与 shadow DOM 相反的是 light DOM, 这基本上就是目前习惯使用的 DOM。 如果添加三个本机按钮和将本机按钮封装到 light DOM 的新自定义按钮并记录 document.querySelectorAll('button').length 将输出 3。虽然可以看到四个按钮,但其中只有三个可用文档的轻根。







如果向全局样式表添加规则,则只有 light DOM 中的按钮才会受到影响。

button {
  border: 4px solid;
}

如果在组件中运行 this.querySelectorAll('*'),将获得零个元素,如果运行 this.shadowRoot.querySelectorAll('*'),将获得一个元素(按钮)。

connectedCallback() {
  console.log(this.querySelectorAll('*').length) // => 0
  console.log(this.shadowRoot.querySelectorAll('*').length) // => 1
}

ES Modules

如果需要,可以将组件文件转换为模块并导出类。现在,可以将其导入到另一个文件中。

class TheModule extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.shadowRoot.textContent = 'A module';
  }
}

customElements.define('the-module', TheModule);

export { TheModule };


HTML Templates

在 JavaScript 中创建 HTML 元素可能会很乏味且令人困惑,尤其是在较大的组件中。 HTML 模板使开发者能够编写可重用的标记块,这些标记块不会在 DOM 中渲染,但仍可以使用 JavaScript 进行引用。

在以下示例中,有一个 自定义元素和一个 ID 为 a-template 的模板。



在组件代码中,克隆模板内容并将其附加到组件的影子根(shadow root)。这允许编写 HTML 并在 JavaScript 代码中使用它。

class TheTemplate extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    let template = document.getElementById('a-template');
    let templateContent = template.content;
    this.shadowRoot.appendChild(templateContent.cloneNode(true));
  }
}

customElements.define('the-template', TheTemplate);

JavaScript 是依赖项吗?

答案不确定。

假设有一个消息组件,该组件根据其类型(信息、警告或警报)具有不同的样式。 如果通过属性将消息正文传递给组件(类似于我们之前对 Star Wars 组件所做的操作),并且使用 JavaScript 来渲染消息,则 JavaScript 就是依赖项。

不过,可以不必这样做。可以通过将内容放在开始标签和结束标签之间来将内容传递给组件。这个过程称为 slot,一大优势是内容现在也存在于文档中。

轻量级 DOM

如果 JavaScript 不起作用,开始标签和结束标签之间的文本仍然会显示,因为它是 light DOM 的一部分。 由于组件中的 JavaScript 代码不运行,因此组件的功能有所不同,而且看起来也可能有所不同,但内容确实就在那里!

That's an info

仅限 JS

如果组件内容首先需要 JavaScript 来运行,那么就不必费心使用 light DOM。 例如,如果创建地图组件,则不必将整个 iframe 传递给 Web Component,因为整个都依赖于 JavaScript。

可以仅传递坐标,Web Component 会渲染地图并嵌入第三方样式和脚本。 在这样的组件中,使用 Shadow DOM 来确保外部样式不会干扰组件样式是有意义的。

声明式 Shadow DOM

Shadow DOM 的一个限制是它完全在客户端上渲染。 声明式 Shadow DOM 消除了这一限制,将 Shadow DOM 引入了服务器。

声明性 Shadow Root 是具有 Shadowrootmode 属性的