HTML web 组件示例
前文 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 看起来也会做类似的事情——但这只是改天的讨论。