编程

HTML web 组件示例

709 2024-01-12 04:38:00

前文 HTML web 组件中,我说过:

web 组件(在浏览器中)的独特能力是,它们可以在 JavaScript 之前进行渲染。React 组件无法做到这一点。

这其中还有许多我想深入解释的东西。

There’s a lot in there I wanted to explain more in-depth, but I just never go to it.

然后,有个读者问道:

据我所知,web 组件需要通过 JS API 进行定义。它怎么可能在 JS 之前运行呢?

好问题。请允许我解释一下我的理解。

使用 web 组件有几种不同的方式。

其中一种便是,引入 JavaScript 来运行使用,并在浏览器中展示。

另一种方式允许您在执行任何 JavaScript 之前在浏览器中显示信息/内容,然后使用 web 组件结构化的 JavaScript 来增强已显示的内容。

我们来使用一个简化的例子,尝试理解一下。

假设我们要创建一个组件来展示用户头像。它是一张图片,当鼠标悬浮其上时,它将显示一个带有全名的 tooltip。

 

如果你使用 React 创建,这个组件大概会是这样:

import Tooltip from "3rd-party-tooltip-on-npm";

function UserAvatar({ src, name }) {
  const [showTooltip, setShowTooltip] = useState(false);
  return (
    <div>
      <img
        src={src}
        alt={`Profile photo of ${name}`}
        width="32"
        height="32"
      />
      {showTooltip && <Tooltip>{name}</Tooltip>}
    </div>
  )
}

/**
 * Example usage:
 *
 * <UserAvatar
 *   src="https://www.jim-nielsen.com/.well-known/avatar"
 *   name="Jim Nielsen"
 * />
 */

这也不错。不过问题是:它需要在浏览器渲染之前引入 JavaScript(React、3rd-party-tooltip-on-npm 以及你自己的 JS 代码)。这是一种要么全有要么全无的方法。如果任何 JavaScript下载、解析和运行失败,最终用户将看不到任何东西。这就是它的样子:

现在,因为这是一个”组件“并且 web 也有组件,是否可以将其移植到 web 组件并移植 React 上的依赖呢?是的,可以。(注意:此代码并不意味着可以用于生产,它只是说明性的)

<user-avatar
  src="https://www.jim-nielsen.com/.well-known/avatar"
  name="Jim Nielsen"
></user-avatar>

<script>
  class UserAvatar extends HTMLElement {
    connectedCallback() {
      const src = this.getAttribute("src");
      const name = this.getAttribute("name");
      this.innerHTML = `
        <div>
          <img
            src="${src}"
            alt="Profile photo of ${name}"
            width="32"
            height="32"
          />
          <!-- Markup/code for the tooltip -->
        </div>
      `;
    }
  }
  customElements.define('user-avatar', UserAvatar);
</script>

我们可以看到这个 web 组件所做的非常相似于 React 组件,但不需要引入 React。

不过,它确实还是需要引入 JavaScript。如果浏览器不能下载、解析及运行任何 JavaScript,屏幕上不会显示任何东西。这就是它们所看到的:

 

按照最初的问题,这种构建 web 组件的方法依赖于 JavaScript。如果没有 JavaScript,此 web 组件就无法在屏幕上显示任何内容。这就是我在原文中所说的“JavaScript Web 组件”的意思。

不过还有另外一种方式可以创建 web 组件。

这种方式就是 Jeremy 所称的 ”HTML web 组件“。它提供的基础功能不需要引入 JavaSript。不过它确实需要组件作者退后一步问:”我要创建的是什么,我如何才能以逐步增强最终用户体验的方式来实现它?”

我们要展示的是显示用户的头像。这并不需要引入 JavaScript,它只是 <img>

是否有办法让 <user-avatar> 提供基础功能,这样在 JavaScript 加载之前,用户就能看见一些东西?

带着这样的想法使用 HTML web 组件,你可以做到。以 HTML 开始,你可以这样编写:

<img
  src="https://www.jim-nielsen.com/.well-known/avatar"
  alt="Profile photo of Jim Nielsen"
  width="32"
  height="32"
  title="Jim Nielsen"
/>

有了这个 HTML,我们有了一个头像图片,并带有一些元信息。注意 title 属性:它将会(在支持的浏览器中)在鼠标悬浮其上时展示 tooltip。因此在 JavaScript 下载、解析并运行之前,桌面端浏览器就能够这么显示:

 

这是我们想要的最终结果吗?不。这个 tooltip 在任何地方都能用吗?不,这是有史以来最好的东西吗™️?不

但这不是重点。重点是,我们从一个基线、核心体验开始,在引入任何 JavaScript 之前,该体验将为广泛的用户代理提供基本功能和内容。

一旦您在普通 HTML 中完成了所有可以提供基线体验的核心元素的工作,就可以开始使用其他功能增强现有标记。

这就是 HTML web 组件的亮点。您可以将该基本功能封装在自定义元素中,然后使用 web 组件的 JavaScript API 来增强标记。

<user-avatar>
  <img
    src="https://www.jim-nielsen.com/.well-known/avatar"
    alt="Profile photo of Jim Nielsen"
    width="32"
    height="32"
    title="Jim Nielsen"
  />
</user-avatar>
<script>
  class UserAvatar extends HTMLElement {
    connectedCallback() {
      // Get the data for the component from exisiting markup
      const $img = this.querySelector("img");
      const src = $img.getAttribute("src");
      const name = $img.getAttribute("title");

      // Create the markup and event listeners for tooltip...

      // Append it to the DOM
      this.insertAdjacentHTML(
        'beforeend', 
        '<!-- code for tooltip here -->'
      );
    }
  }
  customElements.define('user-avatar', UserAvatar);
</script>

同样,这段代码只是为了举例说明,但它展示了如何利用 HTML 提供组件的基本功能,然后用 JavaScript 增强它。

现在你有了一个相当不错的东西。它在 JavaScript 之前显示一些内容,没有任何布局变化。如果/当 JavaScript 加载时,您可以从一开始就获得我们想要的良好体验。

希望这能回答这样一个问题:“我认为 web 组件需要 JavaScript?它们如何在 JavaScript 之前运行?”

这一切都与方法有关。Web 组件为您提供了一种增强方法,您可以提供基本功能,然后根据最终用户的能力逐步增强体验。

尾注:我知道,我知道,关于以上所有内容,还有很多可以说的。例如,像 Remix 这样的JS框架可以让你做与上面非常相似的事情:通过网络提供基本的 HTML,然后用 JavaScript(即 React)增强(即水合)这种体验。RSC 看起来也会做类似的事情——但这只是改天的讨论。