为什么您应该为下一个项目选择 HTMX
在本文中,我们将探讨为什么下次为 Web 应用选择技术栈时应该考虑使用 HTMX 来替代 React。我们将探讨传统 HTTP JSON API + React 带来的复杂性和挑战,以及如何通过使用 HTMX 轻松规避这些挑战。
注意:在本文中,我将讨论 React,但它可以用任何其他前端框架(如 Angular 或 Vue 或 Svelte 或 Solid)代替,但我谈论 React 是因为它是大多数 Web 开发人员默认使用的默认技术。
HTMX 到底是什么
如果您还不了解,HTMX是一个小型浏览器 (JS) 库,它通过一些属性扩展了 HTML,允许您使用服务器的响应来更新网页的某些部分。它还允许 HTML 对所有动词(不仅仅是 GET 和 POST)发出 HTTP 请求。
React 解决了什么问题
React 是一个 JavaScript 库,它通过保持用户界面与状态同步来帮助你编写高度交互的应用程序。你告诉它如何渲染给定的状态,每次状态更新时,它都会尽可能高效地重新渲染 UI 以反映状态变化。
每次状态发生变化时,您都会通知库它已发生变化,并提供新的状态,它将处理 UI 更新。
需要本地内存状态的高交互应用程序的示例可以是您可以在网络上找到的各种文本编辑器(VSCode)、拖放看板(如 Trello 或 JIRA)、视频播放器或聊天室。
什么不是这样的应用?你正在创建的待办事项列表,你正在阅读的新闻网站,你正在发布的博客,以及周围的大多数网站。如果我们看看80/20 规则
80%的效果来自20%的原因,80%的结果来自20%的努力。
你可以说,80% 使用 React 的 Web 应用不需要本地状态。而对于需要本地状态的那 20%,你也可以说它只占了应用的一小部分(大约 20%),其余部分只能用 HTML 来表达。
这些数字是编造的,我没有任何研究来支持这一点
React 还解决了哪些问题,使其在现代网站中得到广泛采用
HTML 已经过时了。以前用 HTML 开发应用程序的方式,需要一系列页面、链接和表单,向用户描述给定资源的当前状态以及如何更改它。
每次用户与资源交互时,应用程序只能重新加载整个页面以显示资源的新状态。
几年后,FaceBook 推出了 React,这是一个允许开发者创建单页应用(SPA)的 JS 库。导航时不再需要重新加载整个页面,状态更新时也拥有炫酷的过渡效果、给用户的有趣反馈以及其他一些优点,这些都促使 Web 开发者纷纷在其网站上采用 SPA 框架。
复杂性问题
如果您不理解上述架构,不用担心,没什么好理解的。我让 ChantGPT 帮我生成了它,虽然它过于复杂且毫无意义,但它完美地反映了现代 Web 应用的默认基础架构。
一个很酷的编程原则是KISS,它代表保持愚蠢的简单,或者有些人可能会开玩笑地说,保持简单,愚蠢!
现代开发人员默认用于创建 Web 应用程序的当前基础设施和技术堆栈极其复杂,做了很多不必要的事情,只是因为它很酷!
当您自己构建第一个 POC 时,它运行良好,但当您添加更多团队成员,并采用敏捷的方式进行多次迭代和“拥抱”变化时,它就会崩溃,至于原因,我们将在后面讨论。
传统 HTTP JSON API + React 的状态管理问题
在 Web 应用中,你经常需要从数据库获取资源的状态并将其呈现给用户。我们以一个任务管理应用为例。用户有一个任务列表,每个任务都有一个状态:
- 任务标题
- 描述
- 任务完成时的标志
- 截止日期(可选)
我们通常将此状态存储在数据库中,并将此信息呈现给用户,您必须:
- 从用户有权访问的数据库中获取所有任务。
- 可选地转换数据(也许您存储完成日期并
is_completed
根据该日期计算标志)。 - 将数据序列化为 JSON。
- 通过 HTTP 请求获取数据。
- (可选但通常)根据模式验证数据,可能使用 YUP 或 ZOD。
- 将 JSON 转换为状态并使用 Redux、Zustand、react-query 或其他状态管理库将其存储在缓存中。
- 在 HTML 中转换该状态通常是为了弄清楚用户可以对数据做什么。
简而言之,我们正在描述如何在 JavaScript 中呈现所有资源的所有可能状态,在浏览器中下载所述 JavaScript,然后 JavaScript 以 JSON 格式下载一堆数据并将其(如果它知道如何)在浏览器上呈现为 HTML!
向用户显示任务列表需要做大量工作,特别是当任务仅在用户更改时才会更改时,为此,应用程序必须将应用程序置于加载状态,发出另一个 HTTP 请求(PUT 或 PATCH 或 DELETE)使缓存值(状态)无效,然后重新获取它以显示更改的任务。
或者更糟糕的是,当用户更改某些任务时,乐观地更新本地状态并立即显示更改,并在后台执行更新请求,仅在用户成功更新后通知用户更新失败。
这极易出错。对于一个只有你一个人的待办事项应用来说,这种做法或许效果不错,因为应用本身足够小,你可以清晰地记住所有正在发生的事情。但当你的团队规模更大时,尤其是当你把团队分成前端和后端时,沟通不畅就可能引发很多问题。
后端可能会使用is_completed
标记,而前端可能期望is_active
标记。后端可能会将description
处理后的 Markdown 内容发送到 HTML,而前端可能期望它未经处理。后端可能会将标记设置为description
可选,以允许用户在前端不同步时保存草稿,这时你会看到很多Uncaught TypeError: Cannot read properties of undefined (reading 'toLowerCase')
另一方面,在 HTMX 上,您可以直接在模板上渲染 HTML,并在后端语言允许的范围内尽可能地保证类型安全。您只需向浏览器发送相关信息,向用户呈现资源的相应控件,并指示浏览器或 HTMX 如何解释用户操作以及后端对这些操作的响应。所有应用程序状态都是 HTML,这个概念被称为HATEOAS。
传统 HTTP JSON API + React 的文档需求
为了让两个团队(后端和前端)独立工作并通过 HTTP JSON API 进行通信,您需要有适当的 API 文档。您还需要记录如何计算用户可以对给定资源执行哪些操作以显示控件。
这类文档大多编写起来很麻烦,尤其是因为它通常需要在实现之前编写,而此时开发人员尚未完全理解问题的范围,因此前端开发可以并行进行。这通常会导致在开发过程中进行多次更新,以适应开发过程中出现的问题,并可能导致团队之间版本不一致。
您还需要对 API 进行版本控制,并注意不要在非主版本变更中引入重大更改。您不能再在不升级主版本的情况下更改字段名称。您还需要保持 API 的多个版本运行,或者强制前端团队进行调整。
而且大多数情况下,文档都会过时。有些文档需要紧急修复,有些新需求在发布前一天就出现了,这时你的文档就过时了,哪怕只是短暂的更新。你必须记住更新文档,或者更糟糕的是,你创建了一个工单来提醒更新,结果却被其他不了解情况、记录错误的人员拿走了!
重复逻辑问题
对于每个资源,您必须实现授权策略。您必须确定当前用户是否可以将任务46234标记为已完成。您必须在后端代码的某个地方编写此检查。否则,您的应用将面临不安全的直接对象引用风险,或者任何使用 Postman 的人都可以将您的任务标记为已完成。
您还必须在前端实现相同的逻辑,仅当用户有权将其标记为完成时才显示标记按钮(假设您可以与其他用户共享您的任务,但只有您可以更改它们)。
现在,每次此逻辑发生变化时,您都必须在两个应用程序中实现它,并同时发布它或拥有多个版本的 API。
性能问题
为了使用 React 在浏览器中渲染网站,你需要将占用大量内存且解析/处理影响巨大的 React 代码、状态管理库代码、toe UI 库代码、CSS-IN-JS 库代码、应用程序代码以及我们通过 NPM 安装和使用的任何 js 库(我们并不介意安装新包,参见 leftpad 问题)捆绑在一起。这通常会导致 JavaScript 资源通过网络传输。当然,你可以在浏览器中缓存,但在现代敏捷开发中,每个 sprint 至少部署一次,所以这并不能解决任何问题。这会消耗网络流量和电池电量,而这对于移动设备来说是一个经常被忽视的问题。
上述 JavaScript 需要由浏览器解释,从而消耗处理能力和电池。
JavaScript,尤其是 ReactDOM,需要跟踪 DOM 的镜像。在此之上添加普通 DOM、本地状态缓存、所有渲染函数以及所有useMemo
和useCallback
和useState
。此外,还要添加所有需要将所有上下文变量保存在内存中的闭包。而 JavaScript 引擎的内存效率并不高!你经常听到人们抱怨浏览器消耗了多少内存,但他们却低估了自己访问的网站消耗的内存。
所有这些加起来,最终会耗尽用户的电池和内存。当然,你可以付出努力来优化所有这些,或者使用其他库,比如 Svelte,但所有这些努力都可以用于为用户提供更有意义的功能。
服务器端渲染的必要性
近年来,我们见证了诸如 之类的服务器端渲染专用框架的兴起Next.js
。它们的流行凸显了以 HTML 格式交付内容的需求,尤其是出于可访问性优化、性能和搜索引擎优化的原因。
您不想等待浏览器下载 JavaScript 来呈现页面,然后等待 JavaScript 发出 HTTP 请求来获取内容然后呈现它,您希望立即呈现它,特别是对于折叠内容上方的内容。
这又增加了一层复杂性,包括:
- 基础设施,现在你还需要另一台服务器来运行前端应用程序
- 代码更加复杂,包括哪些代码在服务器上运行、哪些代码在浏览器上运行的思维导图
- 部署流程现在更加复杂
- 测试基础设施现在更加复杂
- 现在解决问题变得更加困难,您需要了解问题是出在浏览器上、客户端应用服务器上还是 API 服务器上
解决这些问题
每个 Web 开发社区都使用自己的语言或技术进行开发,并以不同的方式解决这些问题:
- Next.js(以及 Nuxt 等)
- React 服务器组件
- Laravel
- Inertia.JS
- Livewire
- 点网
- Blazor 页面
- Elixir
- Phonix LiveView
- 锈
- Leptos 服务器功能
还有许多我忘记或从未听说过的解决方案!
无论如何,这些解决方案的存在和流行证明了这些问题是真实存在的,并且在 Web 开发人员的日常生活中会遇到。否则他们不会费尽心思去解决这些问题,尤其是以开源的方式!
还有Turbo及其采用的框架,Ruby on Rails、PHP Symphony,以及其他可能以与 HTMX 相同的方式解决相同问题的框架。选择 HTMX 只是个人喜好问题,但你绝对应该了解一下,它和 HTMX 一样酷!
在所有这些中,HTMX 脱颖而出,不仅因为它不会将您锁定在特定的技术上,您只需对模板进行微小的更改即可从 PHP 切换到 Rust,而且因为它完全消除了对有状态组件的需求,或者需要跟踪与资源无关的应用程序的某个状态。
以确认对话框为例。通常的做法是,如果它处于打开状态,则在内存中保存一个本地状态,并根据该状态将其显示给用户。在 HTMX 中,状态本身就是 HTML,这意味着当您点击打开对话框时,您将获取HTMLtasks/{taskId}/confirm-delete
并将其嵌入到 DOM 中。而当它被删除时,您将同时删除对话框和任务!这以一种独特且极其简单的方式解决了上述所有问题,您无需:
- 跟踪状态
- 知道如何呈现对话框
- 记录 API
- 检查用户是否可以删除任务(在前端)
- 你的后端应用程序始终负责
- 你可以获得更好的安全性,因为你不会向浏览器发送不相关的数据并偷偷获取敏感信息
- 您将获得更好的表现
最重要的是,你的应用程序要保持非常简单,只有当它解决用户问题时才允许复杂性!
您只需指示 HTMX 从哪里获取对话框以及将其放置在何处,然后一切就会得到处理!
<!-- the delete button -->
@if ($chirp->user->is(auth()->user()))
<form>
@csrf
@method('delete')
<x-dropdown-link
:component="'button'"
type="submit"
hx-get="{{ route('chirps.confirm-destroy', $chirp) }}"
hx-swap="beforeend"
hx-target="closest .chirp"
>
{{ __('Delete') }}
</x-dropdown-link>
</form>
@endif
<!-- the dialog template -->
<div class="modal fixed z-10 inset-0 overflow-y-auto flex justify-center items-center bg-black bg-opacity-50" style="backdrop-filter: blur(14px);">
<div class="bg-white rounded p-6">
<h2 class="text-xl border-b pb-2 mb-2">Confirm Action</h2>
<p>Are you sure you want to delete this chirp?</p>
<div class="flex justify-end mt-4 gap-4">
<x-secondary-button _="on click remove closest .modal" >
Cancel
</x-secondary-button>
<form>
@csrf
<x-danger-button
hx-delete="{{route('chirps.destroy', $chirp)}}"
hx-target="closest .chirp"
hx-swap="delete">
Delete
</x-danger-button>
</form>
</div>
</div>
</div>
这个例子来自我的 [HTMX 与 Laravel]( https://dev.to/turculaurentiu91/laravel-htmx--g0n ) 教程,快来看看吧!
就像这样,当我们点击删除按钮时,我们指示 HTMX 向端点发出GET请求,chirps/{chirp}/confirm-destroy
并将生成的 HTML 放在最近的父级<div class="chirp">
结束之前(底部)。在删除对话框中,当用户确认后,我们指示 HTMX 向端点发出DELETE请求chirps/{chirp}
,如果成功,我们将删除带有该类的最近父级chirp
。
结论
在不断发展的 Web 开发领域,看到像 HTMX 这样的工具倡导简洁和回归本源,令人耳目一新。HTMX 充分利用 HTML 和 HTTP 的强大功能,让开发者能够创建动态 Web 应用程序,而无需承担传统 JavaScript 框架的复杂性和开销。
所以,下次你开始一个新项目或考虑重构现有项目时,不妨试试 HTMX。你可能会惊讶地发现,如此少的资源就能带来如此大的成果。
文章来源:https://dev.to/turculaurentiu91/why-you-should-choose-htmx-for-your-next-project-o7j