HTMX:是在開歷史的倒車嗎?
當(dāng)前位置:點晴教程→知識管理交流
→『 技術(shù)文檔交流 』
被 javascript 全面綁架的前端開發(fā)十幾二十年前,我曾經(jīng)是個自信滿滿的互聯(lián)網(wǎng)開發(fā)者。我可以輕松地使用 django 構(gòu)建 Web UI。頁面上大大小小的重復(fù)部分,我都用 template 或者 fragment 抽象或者封裝。如果需要,我并不排斥撰寫 javascript 來增加交互性: 然而,這種方式構(gòu)建的 UI 會導(dǎo)致用戶和頁面的每次交互都需要后端重新發(fā)送完整的 html 頁面,這既浪費帶寬,交互的方式又笨拙不流暢。因而,一些 ajax 庫便被創(chuàng)造出來提升交互能力。漸漸地,javascript 處理的事情越來越多,就連服務(wù)器端渲染 HTML template 的動作也慢慢遷移到了客戶端。最終,以 react 為代表的響應(yīng)式組件化 UI 的春天來臨了: react 帶給 web 開發(fā)很多革命性的理念:虛擬 dom,單向數(shù)據(jù)流,JSX 以及組件化思維。它讓前端從 HTML 客戶端徹底倒向了 Javascript 客戶端,同時讓后端退出前端渲染的舞臺,把生成 HTML 的主導(dǎo)權(quán)讓渡給前端,自己安安心心地只做數(shù)據(jù) API 的提供方。但當(dāng) javascript 開始接管一切,HTML 不得不成為二等公民后,一切也隨之變了味 —— 連 footer 這樣完全由靜態(tài) HTML 組成的內(nèi)容都要通過 javascript (jsx) 完成。原本豐腴的 HTML 頁面瘦成一道閃電,body 里只剩下一個用來 mount 的根元素。 在 react 席卷前端世界之時,react 的缺陷便一個個暴露出來。于是,新的思想,新的框架,新的生態(tài)工具被創(chuàng)造出來,熱情的前端開發(fā)者架們以一種「逢山開路,遇水搭橋」的方式一路蒙眼狂奔,缺什么補什么:沒有合適的狀態(tài)管理,就創(chuàng)造出 redux;狀態(tài)管理太復(fù)雜,那引入 hooks;js 客戶端對 SEO 不友好,上 SSR …。這樣不斷堆疊解決方案后,最初簡潔明了的方案被硬生生折騰成一個龐雜的縫合怪。此時,我已經(jīng)輕易不敢碰前端了,原本簡簡單單能搞定的事情,現(xiàn)在繁文縟節(jié)一大堆,寫點前端代碼我感覺自己都要被過度的復(fù)雜性壓得透不過起來。 更糟糕的是,由于 javascript 接管一切帶來的前端項目的大型化,使得 typescript 成為了最佳實踐。我并非貶低 typescript,事實上 typescript 是一門設(shè)計良好的語言,它很好地解決了 javascript 在大型前端項目中使用的諸多問題。但我們真的到處都需要「大型」前端項目么?導(dǎo)致前端項目如此龐雜臃腫的根源是什么?最初這些框架的主要目的難道不是為了讓前端更加響應(yīng)式,更容易復(fù)用,更容易表達(dá)么?可如今,react 及那些前前后后崛起的前端框架們,包括 vue,solidjs,svelte 等等,都在以自身的復(fù)雜性迫使前端開發(fā)者,或者說像我這樣的「偽前端開發(fā)者」,不得不把小型項目大型化,簡單項目復(fù)雜化,于是應(yīng)對復(fù)雜項目的 typescript 成為了必然的選擇。 被安在紀(jì)伯倫身上的一句中文名句:”我們已經(jīng)走得太遠(yuǎn),以至于忘了為什么出發(fā)” 形象地描述了這十多年來前端的發(fā)展。我不烙卸嗌僨岸順絳蛟倍鄖岸說南腫錘械鉸?,但像嗡団样,有时候近x鍪竅胛約鶴齙南低程峁┮桓黽蚪嗟� UI —— 只需一茶匙就能裝下的前端需求 —— 卻面對 react 全家桶的復(fù)雜性產(chǎn)生深深的無力感。 差不多一年前,我在做一個后端低代碼的玩具項目時無意發(fā)現(xiàn)了 htmx,一下子就被其純凈的思想深深吸引。彼時我并未深入研究。過去兩周,因為工作的原因我迫切需要做點前端的工作時,我果斷地?fù)炱鹆?htmx 進(jìn)行深入試驗。兩周斷斷續(xù)續(xù)的開發(fā)過程中,我使用 axum (web server) + askama (template) + htmx + tailwindcss 很快地完成了我想做的事情,并且對界面高效地進(jìn)行了好幾版迭代。我自己的感覺是:htmx 即便不能成長為前端的新勢力,它也能重塑所有非前端工程師對前端開發(fā)的信心。對于那些苦前端久矣的開發(fā)者來說,我們也許迎來了前端的 1984 時刻。 回歸 HTML 初心的 HTMX雖然我找不到 HTMX 的名字的來源,根據(jù)它的愿景,我猜測它有 HTML eXtension 的意思。HTMX 認(rèn)為我們應(yīng)該增強和發(fā)展 HTML,HTML 的很多缺陷可以通過更好地 HTML 語義,比如標(biāo)簽的屬性來彌補,而非直接讓 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 的目標(biāo)是簡化前端開發(fā),使開發(fā)者能夠快速、高效地創(chuàng)建交互性強、響應(yīng)迅速的網(wǎng)頁,同時避免涉及大量的 Javascript 或復(fù)雜的前端框架。 當(dāng)你不需要復(fù)雜的前端框架和大量的 javascript 開發(fā)時,你會發(fā)現(xiàn),目前前端所面臨的很多問題都不是問題:不需要把整個頁面 javascript 化,不需要為了解決頁面 javascript 化引入的 SEO 問題,更不需要管理管理復(fù)雜的狀態(tài),以及引入 typescript 來解決工程化的問題。 talk is cheat, show me the code! 我們先來看看 htmx 下,如何實現(xiàn)典型的前端功能:autocomplete。 不要過于震驚,這一小段代碼就是 HTMX 版 autocomplete 的全部代碼。可以看到,HTMX 給普通 HTML 標(biāo)簽增加了幾個重要的屬性:
這幾個是對初學(xué)者而言最有用的屬性,掌握了它們就能處理大部分的頁內(nèi)交互。HTMX 還提供了很多 我們再來看一個復(fù)雜一些的例子: 假設(shè)應(yīng)用展示若干 note books,每個 note book 有若干 notes,每個 note 有詳盡的信息。我們用三欄式展示。用戶點擊最左欄的 book1 時,book1 下的 notes 以分頁的形式展示在第二欄,然后第二欄的第一個 note 的詳情在第三欄展示。 你可以想象一下這樣的頁面和交互需求用 react 該如何完成。 使用 HTMX,我們可以完全依照服務(wù)器渲染的思路設(shè)計,不必過多考慮客戶端如何維持狀態(tài),如何動態(tài)刷新。 在第一次生成這個頁面的時候,我們可以把 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 屬性,不光是第一次頁面渲染的結(jié)果有了,頁面也能根據(jù)用戶的點擊進(jìn)行更新。比如用戶點擊 book2,它會觸發(fā)一個 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> ... 這個結(jié)果會被 HTMX 渲染到 同時,因為返回的 一個事件導(dǎo)致頁面多處更新,這種并不簡單的處理,我們用 HTMX 輕松搞定了。 這里我們引入了一個新的東西:特殊的 HTTP 頭 HX-Trigger。HTMX 定義了很多新的 HTTP header,用于客戶端和服務(wù)器交互額外信息。這里的 你可能會對這段代碼感到疑惑: <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,就實現(xiàn)了整個三欄加載和更新邏輯。我們用圖把整個邏輯梳理一下: 是不是相當(dāng)簡潔?你是愿意撰寫這樣的代碼,還是原意從 npm init 開始,一步步設(shè)置 react 全家桶,最終寫上一大堆組件,維護一系列狀態(tài),才能達(dá)成相同的目標(biāo)? 對于上述這樣一個事件多處更新的場景,使用事件機制是我個人比較喜歡的實現(xiàn)。其實 HTMX 也提供了其他解決方案,比如使用 回顧上述兩個例子,我們可以看到,在使用 HTMX 后,大量的邏輯依舊保留在后端,就像十幾年前我們在 rails/django 里處理的那樣。我們把一個個 template / fragment 拆分到組件級別,然后把服務(wù)器渲染好的 HTML 傳遞給客戶端。只不過,有了 HTMX 后,我們可以很輕松地實現(xiàn)響應(yīng)式前端,所有的操作都可以以你需要的粒度更新在頁面的任何位置。 由于 HTMX 用標(biāo)簽屬性這樣一種很舒服的方式來標(biāo)準(zhǔn)化基本的客戶端/服務(wù)器間的操作,在大多數(shù)場合下,配合 tailwindcss 這樣的 CCS 工具箱,構(gòu)建前端只需要和 HTML 打交道。在我做項目時,我基本上就是找 flowbite 這樣的網(wǎng)站上的某個組件的示例代碼,稍作修改使其模板化,再把這些模板整合起來,一個個頁面就構(gòu)建出來了。我再也不需要拘泥于究竟要做 SPA 還是 MPA,一切根據(jù)需求隨心而動。 當(dāng)然,使用 HTMX 也可能會帶來一些耦合性問題 —— 這并非 HTMX 的鍋,而是自 PHP 起,所有做服務(wù)端渲染 HTML 的后端都會帶來的問題:邏輯層和表現(xiàn)層的耦合,以及多端的支持。 邏輯層和表現(xiàn)層的耦合可以通過更好地架構(gòu)設(shè)計(或者引入合適的框架)來避免,我們放下不表。多端的支持可以通過服務(wù)器對內(nèi)容協(xié)商的支持而得到支持。比如 web 端,可以發(fā)送 總結(jié)HTMX 為非前端工程師重新打開了前端開發(fā)的大門。如果你不是開發(fā)像 spreadsheet,google map 這樣的重交互應(yīng)用,基本上,你都能很好地用 HTMX 來取代現(xiàn)有的前端開發(fā)框架,重新回到以 HTML 為中心的輕量級前端開發(fā)上。你不必拘泥于客戶端究竟該實現(xiàn)成 SPA 還是 MPA,可以用最合適的方式路由,最自然的方式展示數(shù)據(jù),讓用戶跟數(shù)據(jù)交互(無論是增刪改查還是其他什么動作)。 目前,HTMX 的生態(tài)還剛剛起步,我非常期待主流的后端框架對其進(jìn)行深度的支持甚至整合。我相信隨著 HTMX 的價值被不斷發(fā)掘出來,最終,非前端開發(fā)者可以重拾信心,無痛開發(fā)一個包含 web 前端的完整的產(chǎn)品。 該文章在 2023/11/18 17:47:23 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |