HTMX 證明了對不同方法的需求。它簡單而巧妙,但功能有限。控制邏輯最終分佈在 HTML 屬性和後端處理程序之間。它沒有響應式狀態來支援複雜的 UI 流程,而且您的應用程式並非真正「即時」——它仍然需要客戶端程式碼來進行無需用戶互動的更新。
我把它提升到了一個新的維度:Go 語言的集中控制、靈活的路由系統、響應式設計以及跨 UI 的協調更新。它擁有 React 的強大功能,卻沒有它的混亂和 npm 的包袱,還擁有一些獨特的功能。
為了展示實際應用,讓我們建立一個動態、響應式的儀錶板應用程式——完全用 Go 編寫。在此過程中,您將了解框架的各個核心部分是如何協同工作的:從即時 HTML 更新和事件處理到狀態管理、並發控制和導覽。
免責聲明。
省略了一些程式碼,完整版本可在此處取得
dev.to 不支援
templ
突出顯示,因此我將貼上來自 vim 的螢幕截圖(順便說一下)。為了演示目的,我故意省略了錯誤處理以減少 LOC;但這不是應該做的事情!
這是一個深入的探索,它能為你提供足夠的知識,讓你開始建立自己的專案。如果你只想了解大概情況,可以滾動瀏覽,看看 GIF,如果你覺得有什麼有趣的地方,可以繼續閱讀上面的部分。
./page_template.templ
此應用程式有一個包含多個路徑變體的頁面,因此不需要單獨的範本。
儘管如此,將焦點分開還是不錯的。
頁面必須向模板提供head
和body
內容:
兩點說明:
我們包括框架的資產;這至關重要。
我們不僅使用了<link rel="stylesheet" href="...">
,還使用了doors.ImportExternalStyle
,它還能收集資訊。 CSP標頭產生。預設情況下,CSP 是禁用的,但這為我們做好了準備。
doors.Import...
處理本地、嵌入和外部的 CSS 和 JS 資源。對於 JavaScript/TypeScript 模組,它啟用建置/打包步驟並產生導入映射。
./page.templ
在doors中,URI 被解碼為路徑模型。它支援路徑變體、參數和查詢值。
我們的路徑有兩種變體:
/
位置選擇器
/:Id
一個參數:
以及兩個查詢值:
預測天數
單位(公制/英制)
我們現在將省略查詢值,稍後再新增它們。
我們的路徑模型:
此框架使用path
標籤將請求路徑與提供的模式進行比對。
匹配變體的欄位設定為 true。
路徑結構被包裝在狀態原語( Beam )中並傳遞給頁面渲染函數::
為了與框架相容,頁面類型必須實作Render(),返回一個元件,並接受一個帶有路徑模型的Beam 。
路徑匹配時執行的函數。它讀取請求資料(doors.RPage)並選擇回應(doors.PageRouter)。
在我們的例子中,這很簡單:
doors.PageRouter
也支援軟(內部)和硬(HTTP)重定向以及提供靜態頁面。
./main.go
建立一個門路由器,提供一個頁面處理程序,並啟動伺服器。
注意doors.Router
是如何直接插入 Go 標準伺服器的! Go 太棒了。
我們還在直播!
./location_selector.templ
編寫搜尋片段並將其呈現在頁面上。
片段是具有 Render() templ.Component 方法的結構
將事件掛鉤附加到輸入欄位:
doors.AInput
為該元素和事件建立一個臨時的私有端點。
查詢並呈現搜尋結果。
每個
doors.Door
在 goroutine 池上並行渲染,因此渲染期間的資料查詢通常不會減慢頁面渲染速度。
./page.templ
稍微想一下。我們剛剛實現了動態搜尋,無需自訂 JS 或 API,並且只在一個控制流中實現。
您不想將每個擊鍵傳輸到伺服器。
在輸入配置中新增一行以使用Scopes API啟用去抖動過濾:
使用去抖動功能後,重複值出現的可能性會更大。在這種罕見的情況下,用相同的資料更新搜尋結果讓我很不爽。加入一個簡單的檢查來防止這種情況發生:
doors保證同一個鉤子的呼叫按順序執行,因此
prevValue
沒有並發存取問題。
實際上,回應不是即時的,因此要向使用者指示進度。
PicoCSS 為此提供了一個屬性。使用Indication API在待處理作業期間切換它。
最終程式碼:
去抖動和指示一起(模擬延遲):
所有鉤子觸發的變更應用於客戶端後,該指示將清除。
國家和城市選擇只能使用Door ,無需任何反應狀態。
然而,在多步驟表單和複雜 UI 中,這種「低階」方法會將邏輯分散到各個處理程序中,損害可讀性和可偵錯性。在這種情況下,單一資料來源可以顯著減少心理負擔。
加入源光束並在渲染函數中訂閱它:
國家選擇器元件(之前位於主渲染函數內):
顯示所選國家和重置按鈕:
點選選項更新光束:
BlockingScope
會在處理前一個事件時取消所有新事件。它減少了不必要的請求,並明確了意圖。
另外,請注意,我們對所有搜尋選項使用了相同的範圍集,這實際上意味著來自所有處理程序的事件都通過單一管道,只允許一個活動處理程序。
讓我們看看選擇如何與反應狀態一起工作:
哎呀。搜尋結果未清除。這很正常;我們沒有清除它們。
使固定:
結果:
國家選擇器是抽像地點選擇器的原型。暫時將其註解掉。
計劃:
將來源光束新增至保存組合國家和城市資料的位置選擇器。
為國家和城市價值得出單獨的光束。
將我們之前的國家選擇器轉換為抽象的“地點”選擇器。
使用地點選擇器編寫位置選擇器渲染函數。
我們走吧!
源 Beam是原始值,可以更新、變異和觀察。 Beam 只能被觀察。
結構:
我們之前的國家選擇器的方法進行了最小程度的修改(見評論):
主要渲染:
顯示所選內容:
選擇地點:
輸入:
和選項:
具有反應狀態的動態形式:
Beam是一種帶有值流的通訊原語。您可以直接觀察它,也可以使用doors.Sub
/ doors.Inject
來渲染一個會在變更時自動更新的 Door。
它具有一些重要屬性:
僅在值變更時觸發訂閱者。預設情況下,它使用==
來判斷是否需要更新;您可以提供自訂的相等函數。
與渲染同步。在渲染過程中,所有參與節點都會觀察到相同的值。
自上而下地傳播更改。換句話說,負責 DOM 中更重要部分的訂閱者將首先被觸發。
取消過時傳播。如果值在傳播過程中發生變化,則取消過時傳播(如果需要,可使用來源光束上的NoSkip
覆蓋)。
派生光束以群組的形式更新。訂閱處理程序在 goroutine 池上並行執行。
所有這些屬性結合在一起就使得它按預期工作 - 您幾乎不需要考慮它。
表單中缺少鍵盤支援很煩人。
加入鍵盤支援:
自動聚焦輸入。
Tab 並輸入選項的支援。
透過 JS 根據 ID 獲得焦點:
更好的:使其成為可重複使用的元件:
光滑。
並在輸入後渲染:
doors.Script
非常棒。它將內聯腳本轉換為精簡版(如果沒有其他配置),並可快取 src 腳本,保護全域作用域,並啟用await
。另外,如果你提供type="application/typescript"
屬性,它還能編譯 TypeScript。
附加一個鍵事件鉤子並將tabindex
新增至選項:
啟用鍵盤控制:
以下是頁面程式碼:
它始終顯示位置選擇器。記住,我們有兩種路徑變體: /
和/:Id
。後者用於選擇位置時。
現在看看這個傢伙:
你大概也猜到這是怎麼回事了(路徑訂閱)。我們也會新增查詢參數,但我們不希望每次查詢更改時整個頁面都重新渲染,所以derive :
然後訂閱:
顯示選定的城市或 404:
現在位置選擇器必須動態改變路徑。
在位置選擇器中新增應用依賴項:
實現提交功能:
為選擇器提供應用功能:
我們可以不傳遞路徑變更函數,而是直接渲染一個連結。此範例展示如何以程式設計方式進行導航。
透過路徑變異選擇的位置:
在 doors 中,你可以使用$d.on(...)
在前端註冊一個 JS 處理程序,並使用doors.Call(...)
在 Go 中呼叫它。實作動態標題非常簡單。
但是,您只需使用預製的doors.Head
元件即可:
除了
title
之外,它還支援<meta>
標籤。
實際上,提交處理程序不會立即回應。如果我們在處理過程中與 UI 互動會怎麼樣?
讓我們模擬衝突的行為:
它並沒有改變結果,但卻導致了奇怪的 UI 行為。
使用Scopes API中的Concurrent Scope可以輕鬆緩解此問題。
並發範圍只能被具有相同群組 ID 的事件「佔用」。
在位置選擇器中新增並發範圍:
在地點選擇器中新增父範圍屬性:
將群組 ID 1 指派給兩個位置選擇器:
在“更改位置”按鈕上使用它:
最後,將其套用到具有不同群組 ID 的提交按鈕:
此設定可確保提交事件或更改位置事件可以執行,而不是同時執行:
由於城市和國家屬於同一組,因此變更不會相互影響:
由於框架的非阻塞事件模型,並發控制是必要的。這是Doors相較於 Phoenix LiveView 或 Blazor Server 的一大優勢,它能夠在不犧牲使用者體驗的情況下實現高度互動的 UI。
在天氣 API 中,除了城市之外,我們還有兩個變數:單位(公制/英制)和預報天數。
將其加入到我們的路徑模型中:
./page.templ
注意我使用了引用類型。否則,查詢參數將獲得零值並始終出現在 URI 中。
./dashboard.templ
為了讓頁面保持簡單,我們將儀表板移到單獨的片段。
儀表板取決於位置 ID(頁面已提供)以及天數和單位查詢參數:
我們從以下路徑中得出這些:
在頁面上呈現儀表板:
為了顯示位置選擇器,我們需要渲染一個指向/
連結。如果查詢參數能夠持久化就更好了,所以我們根據設定光束產生連結:
AHref
也支援Scopes和Indication API
透過動態連結切換到位置選擇器:
點擊後,查詢參數顯示為預設值。還可以,但並不理想。
為預設值提供 nil,以使行為保持一致:
現在我很高興:
渲染一個連結來回切換單位:
加入一些樣式並渲染單位切換器:
查詢參數切換:
預測天數連結必須保留單位查詢值。為了避免不必要的更新,請為下列單位衍生一個波束:
訂閱菜單吧:
並保持單位查詢值:
反應選單:
doors不會將整個 DOM 保存在記憶體中。使用波束推導,你可以明確地將特定的 HTML 部分與特定的資料片段綁定。差異資料,而不是 DOM。
如果您在doors.AHref
中配置了活動連結突出顯示,客戶端可以自動套用它:
預設情況下,它會檢查整個路徑和所有查詢值以套用指示,但您可以配置更窄的匹配策略。
活動連結突出顯示:
路徑作為狀態非常強大。它具有聲明性、類型安全,並且不限制路徑到 HTML 的映射方式。
讓我們準備一個溫度圖並看看情況如何。
我將使用doors.Inject
輔助函數來取代doors.Sub
。它本質上的作用相同,但它不是對函數進行求值,而是使用包含 beam 值的上下文來渲染子級。
為了提供生成的 SVG,我將使用doors.ARawSrc
,它使用自訂請求處理程序建立src
屬性:「
doors.ARawSrc
(以及doors.ASrc
、doors.AFileHref
、doors.ARawFileHref
)使用鉤子機制並私下提供資源。
帶有動態 SVG 的溫度折線圖:
抽象圖表元件,以便它可以被所有圖表重複使用:
所有圖表:
影像預載器+參數開關指示:
在所有選單連結上包含此指示:
帶有預先載入器的圖表:
您可能已經注意到天氣和濕度並不取決於units
值。
像往常一樣近似 - 推導出不依賴單位的光束:
此外,我們不需要觸發該指示,因此請使其更具體:
具有days
變化的圖表元件:
最終結果(慢速網路模擬):
頁面大小:
其中~13 KB 是 PicoCSS,~10 KB 是門客戶端。
坦白說,我討厭寫 UI,它總是讓我感覺自己做錯了什麼。感覺就像擁有 10 種不同的工具,它們都不是為某項工作而設計的,所以你必須以一種笨拙的方式組合它們才能完成某項工作。這感覺已經不像程式了。
有了門,感覺就像程式設計一樣。可預測的解決路徑、已知的結果、自由。
我很享受編寫這個小應用程式的每一分鐘,我希望您能抽出時間親自體驗一下。
特別感謝 Adrian Hesketh 在templ上所做的出色工作,使得這個專案成為可能。
原文出處:https://dev.to/derstruct/go-devs-just-got-superpowers-2lb3