HTMX:是在开历史的倒车吗?
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
被 javascript 全面绑架的前端开发十几二十年前,我曾经是个自信满满的互联网开发者。我可以轻松地使用 django 构建 Web UI。页面上大大小小的重复部分,我都用 template 或者 fragment 抽象或者封装。如果需要,我并不排斥撰写 javascript 来增加交互性: 然而,这种方式构建的 UI 会导致用户和页面的每次交互都需要后端重新发送完整的 html 页面,这既浪费带宽,交互的方式又笨拙不流畅。因而,一些 ajax 库便被创造出来提升交互能力。渐渐地,javascript 处理的事情越来越多,就连服务器端渲染 HTML template 的动作也慢慢迁移到了客户端。最终,以 react 为代表的响应式组件化 UI 的春天来临了: react 带给 web 开发很多革命性的理念:虚拟 dom,单向数据流,JSX 以及组件化思维。它让前端从 HTML 客户端彻底倒向了 Javascript 客户端,同时让后端退出前端渲染的舞台,把生成 HTML 的主导权让渡给前端,自己安安心心地只做数据 API 的提供方。但当 javascript 开始接管一切,HTML 不得不成为二等公民后,一切也随之变了味 —— 连 footer 这样完全由静态 HTML 组成的内容都要通过 javascript (jsx) 完成。原本丰腴的 HTML 页面瘦成一道闪电,body 里只剩下一个用来 mount 的根元素。 在 react 席卷前端世界之时,react 的缺陷便一个个暴露出来。于是,新的思想,新的框架,新的生态工具被创造出来,热情的前端开发者架们以一种「逢山开路,遇水搭桥」的方式一路蒙眼狂奔,缺什么补什么:没有合适的状态管理,就创造出 redux;状态管理太复杂,那引入 hooks;js 客户端对 SEO 不友好,上 SSR …。这样不断堆叠解决方案后,最初简洁明了的方案被硬生生折腾成一个庞杂的缝合怪。此时,我已经轻易不敢碰前端了,原本简简单单能搞定的事情,现在繁文缛节一大堆,写点前端代码我感觉自己都要被过度的复杂性压得透不过起来。 更糟糕的是,由于 javascript 接管一切带来的前端项目的大型化,使得 typescript 成为了最佳实践。我并非贬低 typescript,事实上 typescript 是一门设计良好的语言,它很好地解决了 javascript 在大型前端项目中使用的诸多问题。但我们真的到处都需要「大型」前端项目么?导致前端项目如此庞杂臃肿的根源是什么?最初这些框架的主要目的难道不是为了让前端更加响应式,更容易复用,更容易表达么?可如今,react 及那些前前后后崛起的前端框架们,包括 vue,solidjs,svelte 等等,都在以自身的复杂性迫使前端开发者,或者说像我这样的「伪前端开发者」,不得不把小型项目大型化,简单项目复杂化,于是应对复杂项目的 typescript 成为了必然的选择。 被安在纪伯伦身上的一句中文名句:”我们已经走得太远,以至于忘了为什么出发” 形象地描述了这十多年来前端的发展。我不烙卸嗌偾岸顺绦蛟倍郧岸说南肿锤械铰猓裎艺庋惺焙蚪鼋鍪窍胛约鹤龅南低程峁┮桓黾蚪嗟� UI —— 只需一茶匙就能装下的前端需求 —— 却面对 react 全家桶的复杂性产生深深的无力感。 差不多一年前,我在做一个后端低代码的玩具项目时无意发现了 htmx,一下子就被其纯净的思想深深吸引。彼时我并未深入研究。过去两周,因为工作的原因我迫切需要做点前端的工作时,我果断地捡起了 htmx 进行深入试验。两周断断续续的开发过程中,我使用 axum (web server) + askama (template) + htmx + tailwindcss 很快地完成了我想做的事情,并且对界面高效地进行了好几版迭代。我自己的感觉是:htmx 即便不能成长为前端的新势力,它也能重塑所有非前端工程师对前端开发的信心。对于那些苦前端久矣的开发者来说,我们也许迎来了前端的 1984 时刻。 回归 HTML 初心的 HTMX虽然我找不到 HTMX 的名字的来源,根据它的愿景,我猜测它有 HTML eXtension 的意思。HTMX 认为我们应该增强和发展 HTML,HTML 的很多缺陷可以通过更好地 HTML 语义,比如标签的属性来弥补,而非直接让 javascript 取代 HTML。这是 htmx.org 上的直接介绍: htmx gives you access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext HTMX 的核心愿景和特点包括:
我们可以看到,HTMX 的目标是简化前端开发,使开发者能够快速、高效地创建交互性强、响应迅速的网页,同时避免涉及大量的 Javascript 或复杂的前端框架。 当你不需要复杂的前端框架和大量的 javascript 开发时,你会发现,目前前端所面临的很多问题都不是问题:不需要把整个页面 javascript 化,不需要为了解决页面 javascript 化引入的 SEO 问题,更不需要管理管理复杂的状态,以及引入 typescript 来解决工程化的问题。 talk is cheat, show me the code! 我们先来看看 htmx 下,如何实现典型的前端功能:autocomplete。 不要过于震惊,这一小段代码就是 HTMX 版 autocomplete 的全部代码。可以看到,HTMX 给普通 HTML 标签增加了几个重要的属性:
这几个是对初学者而言最有用的属性,掌握了它们就能处理大部分的页内交互。HTMX 还提供了很多 我们再来看一个复杂一些的例子: 假设应用展示若干 note books,每个 note book 有若干 notes,每个 note 有详尽的信息。我们用三栏式展示。用户点击最左栏的 book1 时,book1 下的 notes 以分页的形式展示在第二栏,然后第二栏的第一个 note 的详情在第三栏展示。 你可以想象一下这样的页面和交互需求用 react 该如何完成。 使用 HTMX,我们可以完全依照服务器渲染的思路设计,不必过多考虑客户端如何维持状态,如何动态刷新。 在第一次生成这个页面的时候,我们可以把 book1 下面的所有 note summary 展示出来,然后再把 book1 note1 下面的 detail 也展示出来。几个部分的模板片段如下。首先是左栏: <ul> {% for book in books %} <li> <a hx-get="/books/{{book.id}}" hx-target="#note-list">{{book.name}}</a> </li> {% endfor %} </ul> 然后中栏: <div id="note-list"> {% for note in current_book.notes %} <div onclick="htmx.trigger('#note-detail', 'loadNote', {id: '{{note.id}}'})"> <h2>{{note.title}}</h2> <p>{{note.summary}}</p> </div> {% endfor %} </div> 最后右栏: <div id="note-detail" _="on loadNote(data) from body htmx.ajax('GET', `/notes/${data.id}`, '#note-detail')"> <h3>{{title}}</h3> <p>{{detail}}</p> </div> 这样简简单单几个模板,辅以额外的 HTMX 属性,不光是第一次页面渲染的结果有了,页面也能根据用户的点击进行更新。比如用户点击 book2,它会触发一个 GET 请求,访问 200 OK HX-Trigger: {"loadNote": {"id": "book2id1"}} Content-Type: text/html <h2>Hello 1</h2> <p>World 1</p> </div> <div onclick="htmx.trigger('#note-detail', 'loadNote', {id: 'book2id2'})"> <h2>Hello 2</h2> <p>World 2</p> </div> ... 这个结果会被 HTMX 渲染到 同时,因为返回的 一个事件导致页面多处更新,这种并不简单的处理,我们用 HTMX 轻松搞定了。 这里我们引入了一个新的东西:特殊的 HTTP 头 HX-Trigger。HTMX 定义了很多新的 HTTP header,用于客户端和服务器交互额外信息。这里的 你可能会对这段代码感到疑惑: <div id="note-detail" _="on loadNote(data) from body htmx.ajax('GET', `/notes/${data.id}`, '#note-detail')" > 它是 HTMX 的 hyperscript 的表述,等价于: document.body.addEventListener("loadNote", function(e){ htmx.ajax('GET', `/notes/${e.detail.id`, '#node-detail'); }) 到目前为止,我们写了两三行非常简单的 javascript,就实现了整个三栏加载和更新逻辑。我们用图把整个逻辑梳理一下: 是不是相当简洁?你是愿意撰写这样的代码,还是原意从 npm init 开始,一步步设置 react 全家桶,最终写上一大堆组件,维护一系列状态,才能达成相同的目标? 对于上述这样一个事件多处更新的场景,使用事件机制是我个人比较喜欢的实现。其实 HTMX 也提供了其他解决方案,比如使用 回顾上述两个例子,我们可以看到,在使用 HTMX 后,大量的逻辑依旧保留在后端,就像十几年前我们在 rails/django 里处理的那样。我们把一个个 template / fragment 拆分到组件级别,然后把服务器渲染好的 HTML 传递给客户端。只不过,有了 HTMX 后,我们可以很轻松地实现响应式前端,所有的操作都可以以你需要的粒度更新在页面的任何位置。 由于 HTMX 用标签属性这样一种很舒服的方式来标准化基本的客户端/服务器间的操作,在大多数场合下,配合 tailwindcss 这样的 CCS 工具箱,构建前端只需要和 HTML 打交道。在我做项目时,我基本上就是找 flowbite 这样的网站上的某个组件的示例代码,稍作修改使其模板化,再把这些模板整合起来,一个个页面就构建出来了。我再也不需要拘泥于究竟要做 SPA 还是 MPA,一切根据需求随心而动。 当然,使用 HTMX 也可能会带来一些耦合性问题 —— 这并非 HTMX 的锅,而是自 PHP 起,所有做服务端渲染 HTML 的后端都会带来的问题:逻辑层和表现层的耦合,以及多端的支持。 逻辑层和表现层的耦合可以通过更好地架构设计(或者引入合适的框架)来避免,我们放下不表。多端的支持可以通过服务器对内容协商的支持而得到支持。比如 web 端,可以发送 总结HTMX 为非前端工程师重新打开了前端开发的大门。如果你不是开发像 spreadsheet,google map 这样的重交互应用,基本上,你都能很好地用 HTMX 来取代现有的前端开发框架,重新回到以 HTML 为中心的轻量级前端开发上。你不必拘泥于客户端究竟该实现成 SPA 还是 MPA,可以用最合适的方式路由,最自然的方式展示数据,让用户跟数据交互(无论是增删改查还是其他什么动作)。 目前,HTMX 的生态还刚刚起步,我非常期待主流的后端框架对其进行深度的支持甚至整合。我相信随着 HTMX 的价值被不断发掘出来,最终,非前端开发者可以重拾信心,无痛开发一个包含 web 前端的完整的产品。 该文章在 2023/11/18 17:47:23 编辑过 |
关键字查询
相关文章
正在查询... |