对 htmx 感兴趣的人经常向我们询问组件库。 React 和其他 JavaScript 框架拥有优秀的预构建组件生态系统,可以导入到您的项目中;htmx 并没有类似的东西。
首先要理解的最重要的事情是,htmx 并不排除您使用任何东西。 因为基于 htmx 的网站通常是多页应用程序,每个页面都是一个空白画布,您可以根据需要导入多少或多么少的 JavaScript。 如果您的应用程序主要是超媒体,但您希望在某一页使用基于 React 的交互式日历,只需在那一页使用 script 标签导入它即可。
我们有时将这种模式称为“交互岛屿”——它在我们的解释文章中被引用这里、这里 和 这里。 与 JS 框架不同,后者大多彼此不兼容,使用 htmx 的岛屿不会将您锁定在任何特定范式中。
但是,您可以使用 htmx 重复使用复杂前端功能,还有第二种方式,那就是 Web Components!
假设您有一个表格,显示每个人报名参加什么嘉年华游乐设施:
| 姓名 | 旋转木马 | 过山车 |
|---|---|---|
| Alex | 是 | 否 |
| Sophia | 是 | 是 |
Alex 愿意乘坐旋转木马但不愿意乘坐过山车,因为他害怕;Sophia 对两者都不害怕。
我将其构建为常规 HTML 表格(省略结束标签 以清晰):
<table>
<tr><th>Name <th>Carousel <th>Roller Coaster
<tr><td>Alex <td>Yes <td>No
<tr><td>Sophia <td>Yes <td>Yes
</table>
现在想象我们想使那些行可编辑。 这是一个经典的情况,人们会转向框架,但我们可以用超媒体实现吗? 当然! 以下是一个天真的想法:
<form hx-put=/carnival>
<table>
<tr>
<th>Name
<th>Carousel
<th>Roller Coaster
</tr>
<tr>
<td>Alex
<td><select name="alex-carousel"> <option selected>Yes <option>No <option> Maybe</select>
<td><select name="alex-roller"> <option>Yes <option selected>No <option> Maybe</select>
</tr>
<tr>
<td>Sophia
<td><select name="sophia-carousel"> <option selected>Yes <option>No <option> Maybe</select>
<td><select name="sophia-roller"> <option selected>Yes <option>No <option> Maybe</select>
</tr>
</table>
<button>Save</button>
</form>
| 姓名 | 旋转木马 | 过山车 |
|---|---|---|
| Alex | ||
| Sophia |
这还不错!
保存按钮将提交表格中的所有数据,服务器将响应一个反映更新状态的新表格。
我们还可以使用 CSS 使 <select> 符合我们的设计语言。
但是很容易看出这可能会变得笨拙——随着更多列、更多行和每个单元格中更多选项,每次发送所有这些信息都会变得昂贵。
让我们使用 Web 组件移除所有这些冗余!
<form hx-put=/carnival>
<table>
<tr>
<th>Name
<th>Carousel
<th>Roller Coaster
</tr>
<tr>
<td>Alex
<td><edit-cell name="alex-carousel" value="Yes"></edit-cell>
<td><edit-cell name="alex-roller" value="No"></edit-cell>
</tr>
<tr>
<td>Sophia
<td><edit-cell name="sophia-carousel" value="Yes"></edit-cell>
<td><edit-cell name="sophia-roller" value="Yes"></edit-cell>
</tr>
</table>
<button>Save</button>
</form>
我们仍然有一个完全声明式的 HATEOAS 接口——当前状态(value 属性)和该状态的可能操作(<form> 和 <edit-cell> 元素)都高效编码在超文本中——只是现在我们更简洁地表达了相同的想法。
htmx 可以添加或移除行(或者更好的是,整个表格),使用 <edit-cell> Web 组件,就好像 <edit-cell> 是内置的 HTML 元素一样。
您可能已经注意到,我没有包含 <edit-cell> 的实现细节(尽管您可以,当然,查看此页面的源代码来查看它们)。
那是因为它们不重要!
无论 Web 组件是由您、您的团队成员还是库作者编写的,它都可以像内置 HTML 元素一样使用,htmx 会很好地处理它。
JavaScript 框架支持 Web Components 的大多数问题不适用于 htmx。
Web Components 具有基于 DOM 的生命周期,因此对于经常在 DOM 之外操作元素的 JavaScript 框架来说,它们很难处理。 框架必须考虑一些奇怪且可以说有 bug 的 API,这些 API 对于原生 DOM 元素和自定义元素的行为不同。 在 htmx,我们同意 SvelteJS 创建者 Rich Harris 的观点:“Web Components 不是构建 Web 框架的有用原语。”
好消息是,htmx 并不是一个真正的 JavaScript Web 框架。
自定义元素的基于 DOM 的生命周期在 htmx 中工作得很好,因为 htmx 中的一切都有基于 DOM 的生命周期——我们从服务器获取东西,然后将其添加到 DOM。
htmx 的默认交换样式只是设置 .innerHTML,这对绝大多数用户来说工作得很好。
这并不是说 htmx 不需要适应奇怪的 Web 组件边缘情况。 我们的社区成员和驻地 WC 专家 Katrina Scialdone 合并了 htmx 2.0 的 Shadow DOM 支持,这让 htmx 处理 Web 组件的实现细节, 并且支持它偶尔 令人沮丧。 但能够处理 Shadow DOM 和 “Light DOM” 是 htmx 的一个不错功能,并且因为 htmx 并没有做太多事情,支持负担相对较小。
几年前,W3C 贡献者(以及 Web 组件倡导者,我认为)Lea Verou 在一篇关于 “Web Components 的失败承诺” 的博客文章中写道:
主要问题是这些组件的设计没有以适当的尊重对待 HTML。它们没有尽可能接近标准 HTML 元素设计,而是期望为它们编写 JS 才能做任何事情。HTML 只是被当作简写,或者更糟,只是作为标记来指示元素在 DOM 中的位置,所有参数通过 JS 传递。
Lea 指出了一个从 2020 年的视角来看似乎不可能解决的问题:Web Components 针对的尖端 Web 开发者没有编写 HTML,他们编写 JSX,通常使用 React(或 Vue,或其他)。 行为属于 HTML 的想法,在时代精神中,被认为是违反关注点分离; 不尊重 HTML 是最佳实践。
htmx 的相对最近的成功——它本身现在是时代精神中的参与者——提供了一条替代路径:再次认真对待 HTML。 如果您的网站功能主要可以用 大粒度超媒体传输 描述(我们相信大多数可以),那么通过超媒体表达更复杂模式的价值会急剧增加。 随着更多开发者使用 htmx(以及多页架构一般)来构建他们的网站, 也许对 Web Components 的需求会随之增加。
Web Components 在所有地方“只是工作”吗?也许,也许不是。但它们在这里工作。