对 htmx 感兴趣的人常问我们关于组件库的问题。 React 和其他 JavaScript 框架拥有丰富的预构建组件生态系统,可以导入到你的项目中;而 htmx 并没有类似的东西。
首先要理解的最重要一点是,htmx 并不阻止你使用任何东西。 因为基于 htmx 的网站通常是多页面应用,每个页面都是一块空白画布,你可以在上面导入任意多或任意少的 JavaScript。 如果你的应用主要是超媒体,但某个页面需要一个基于 React 的交互式日历,只需在该页面通过 script 标签导入即可。
我们有时称这种模式为“交互性孤岛”——在这里、这里和这里的说明中有提及。 与 JavaScript 框架大多互不兼容不同,在 htmx 中使用“孤岛”不会将你锁定在任何特定的范式中。
但还有第二种方式可以在 htmx 中重用复杂的前端功能,那就是Web Components!
假设你有一个表格,显示每个人报名参加的嘉年华游乐设施:
姓名 | 旋转木马 | 过山车 |
---|---|---|
Alex | 是 | 否 |
Sophia | 是 | 是 |
Alex 愿意玩旋转木马但不愿意玩过山车,因为他害怕;Sophia 两个都不怕。
我将其构建为一个常规的 HTML 表格(为清晰起见,省略了闭合标签):
<table>
<tr><th>姓名 <th>旋转木马 <th>过山车
<tr><td>Alex <td>是 <td>否
<tr><td>Sophia <td>是 <td>是
</table>
现在想象我们想让这些行可编辑。 这是人们通常会求助于框架的经典场景,但我们能用超媒体实现吗? 当然可以! 这里有一个初步想法:
<form hx-put=/carnival>
<table>
<tr>
<th>姓名
<th>旋转木马
<th>过山车
</tr>
<tr>
<td>Alex
<td><select name="alex-carousel"> <option selected>是 <option>否 <option> 可能</select>
<td><select name="alex-roller"> <option>是 <option selected>否 <option> 可能</select>
</tr>
<tr>
<td>Sophia
<td><select name="sophia-carousel"> <option selected>是 <option>否 <option> 可能</select>
<td><select name="sophia-roller"> <option selected>是 <option>否 <option> 可能</select>
</tr>
</table>
<button>保存</button>
</form>
姓名 | 旋转木马 | 过山车 |
---|---|---|
Alex | ||
Sophia |
这还不算太糟!
保存按钮将提交表格中的所有数据,服务器将响应一个反映更新状态的新表格。
我们还可以使用 CSS 让 <select>
元素符合我们的设计语言。
但很容易看出这可能会变得难以管理——随着列数、行数和每个单元格选项的增加,每次发送所有这些信息开始变得代价高昂。
让我们用一个 Web Component 来移除所有这些冗余!
<form hx-put=/carnival>
<table>
<tr>
<th>姓名
<th>旋转木马
<th>过山车
</tr>
<tr>
<td>Alex
<td><edit-cell name="alex-carousel" value="是"></edit-cell>
<td><edit-cell name="alex-roller" value="否"></edit-cell>
</tr>
<tr>
<td>Sophia
<td><edit-cell name="sophia-carousel" value="是"></edit-cell>
<td><edit-cell name="sophia-roller" value="是"></edit-cell>
</tr>
</table>
<button>保存</button>
</form>
我们仍然拥有完全声明式的HATEOAS界面——当前状态(value
属性)和该状态上的可能操作(<form>
和<edit-cell>
元素)都高效地编码在超文本中——只是现在我们以更简洁的方式表达了相同的想法。
htmx 可以添加或删除行(或者更好的是,整个表格),使用 <edit-cell>
Web Component,就像 <edit-cell>
是内置的 HTML 元素一样。
你可能注意到我没有包含 <edit-cell>
的实现细节(不过你当然可以查看本页源代码来查看它们)。
那是因为它们无关紧要!
无论这个 Web Component 是由你、你的队友还是库作者编写的,它都可以像内置 HTML 元素一样使用,而 htmx 会很好地处理它。
许多 JavaScript 框架在支持 Web Components 时遇到的问题并不适用于 htmx。
Web Components 具有基于 DOM 的生命周期,因此对于通常在 DOM 外操作元素的 JavaScript 框架来说难以处理。 框架必须处理一些怪异且可以说有缺陷的 API,这些 API 对于原生 DOM 元素和自定义元素的行为不同。 在 htmx,我们同意SvelteJS 创建者 Rich Harris的观点:“Web Components 不是构建 Web 框架的有用基础。”
好消息是 htmx 并不是真正的 JavaScript Web 框架。
自定义元素的基于 DOM 的生命周期在 htmx 中运行良好,因为 htmx 中的所有内容都有基于 DOM 的生命周期——我们从服务器获取内容,然后将其添加到 DOM 中。
htmx 的默认交换样式是直接设置 .innerHTML
,这对绝大多数用户来说效果很好。
这并不是说 htmx 不需要适应 Web Component 的奇怪边缘情况。 我们的社区成员兼 Web Components 专家 Katrina Scialdone 合并了对 htmx 2.0 的 Shadow DOM 支持,这使得 htmx 可以处理 Web Component 的实现细节, 支持这个功能偶尔会令人沮丧。 但能够同时处理Shadow DOM和“Light DOM”对 htmx 来说是一个很好的特性,并且由于 htmx 本身做的事情并不多,所以它带来的支持负担相对较小。
几年前,W3C 贡献者(也是 Web Component 的支持者,我认为)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 是否“随处可用”?也许行,也许不行。但它们在这里确实能用。