🎯 什麼情境該想到我
當你「想把頁面產生分兩步,先產生與版面無關的邏輯結構,再統一套上外觀」的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:把「呈現什麼」與「長什麼樣」分成兩個步驟,讓整站外觀能集中統一調整。
做法要點:
- 第一步:把領域資料轉成一個與最終外觀無關的「邏輯呈現結構」(描述有哪些欄位、表格、清單等抽象元素)。
- 第二步:把該邏輯結構轉成實際 HTML,套上一致的樣式與版型。
- 想全站換皮或改版型,只需改第二步。
★★ 它的核心價值:把「有什麼」與「長什麼樣」分開
第一步:領域資料 → ★ 邏輯呈現結構(「這是一個表格,有三欄,第二欄要標紅」)
第二步:邏輯結構 → ★ 具體輸出(HTML/JSON/CSV/PDF)
→ ★★ 換版面只改第二步;改內容只改第一步
★★ 今天最大規模的實踐就是「設計系統」:
第一步產生<Card>、<DataTable>、<Badge variant="warning">這種語義元件;
第二步(設計系統的實作)決定它們實際長什麼樣。
★ 改一次設計系統,全站一致地變——這正是兩步驟視圖承諾的東西。
★★ Go 的三種落地方式
// ★ ① 共用 layout + 具名區塊(★ 最輕,html/template 內建)
{{ define "layout" }}
<html><body>
<nav>…</nav>
{{ block "content" . }}{{ end }} // ★ 第二步:骨架
</body></html>
{{ end }}
{{ define "content" }} … {{ end }} // ★ 第一步:這一頁的內容
// ★★ ② 明確的「呈現模型」型別(★ 真正的兩步驟)
type Column struct{ Title string; Align string }
type Cell struct{ Text string; Emphasis string } // ★ 語義,不是樣式
type Table struct{ Columns []Column; Rows [][]Cell }
func orderTable(orders []Order) Table { … } // ★ 第一步:純函式,好測
// → 第二步:一個樣板把 Table 渲染成 HTML;另一個渲染成 CSV
// ★ ③ 元件式(htmx / templ / 前端框架)
// → ★ 每個語義元件有自己的渲染實作★★ ② 的最大好處:第一步是純函式,可以用表格測試窮舉。
「金額為負要標紅」這條規則變成一個斷言 cell.Emphasis == "negative",
不必解析 HTML。
★★ 什麼時候值得做(三個明確訊號)
① ★★ 同一份資料要輸出多種格式
HTML 頁面 + CSV 匯出 + PDF 報表 + Email 版
→ ★ 沒有兩步驟時,四份程式碼各自實作「金額為負要標紅」⇒ 一定會不一致
② ★★ 版面要全站一致地改
「所有的警示標籤改成橘色」→ ★ 改一處 vs 改 47 個樣板
③ ★ 需要對「呈現邏輯」寫測試
→ ★★ 第一步是純函式,測試不必解析 HTML
★★ 它的代價:多一層抽象,而那一層會漏
★ 三個真實的問題:
① ★★ 邏輯結構總是不夠表達力
「這一欄要 sticky」「這個 badge 要有 tooltip」→ ★ 邏輯模型裡沒有這個概念
→ ★★ 於是它長出 CSSClass string 或 Attrs map[string]string 欄位
→ ★ 一旦有了這兩個欄位,「樣式與內容分離」就破功了
② ★ 兩層都要維護
加一個欄位要改「邏輯模型 → 第一步 → 第二步」三處
③ ★★ 除錯多一層
「為什麼這個數字沒有標紅」→ ★ 要先查第一步有沒有設 Emphasis,
再查第二步有沒有處理那個值
★★ 判準:你真的有「多種輸出」或「全站改版」的需求嗎?
沒有(只有一個 HTML 介面,而且版面穩定)→ 樣板視圖 直接來,這是純成本。
★ 實務上「CSV 匯出」是最常見的第二種輸出,而它通常不需要完整的兩步驟——
一個獨立的toCSV(orders)函式就夠了。
★★ 一個常被忽略的替代:讓第一步「只產生資料,不產生呈現」
// ★ 不做完整兩步驟,但做到 80% 的收益:
type OrderRow struct {
ID string
Total Money
IsOverdue bool // ★★ 語義旗標(而不是 CSSClass: "text-red")
IsVIP bool
}
// → ★ 樣板自己決定 IsOverdue 要怎麼呈現
// → ★★ 這就達成了「業務語義與樣式分離」,而不需要一個完整的呈現模型★★ 這是實務上最划算的點:
view struct 只放「業務語義的布林與值」,絕不放 CSS class 或顏色。
→ ★ 樣式的決定完全留在樣板/CSS 裡,而測試可以斷言語義旗標。
★ 兄弟辨析
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
需要各頁外觀差異很大時,共用第二步反而綁手綁腳。
-
多了一層中介結構,較單步視圖複雜。
-
只有少數頁面、外觀不需高度一致時不划算。
-
★★ 沒有「多種輸出」或「全站改版」的需求,這就是純成本。 用 樣板視圖 直接來。
-
★★ 邏輯結構總是不夠表達力,於是長出
CSSClass或Attrs欄位——一旦有了它們,「樣式與內容分離」就破功了。 -
★★ 最划算的替代:view struct 只放業務語義的布林與值(
IsOverdue bool),絕不放 CSS class 或顏色。這達成 80% 的收益而沒有第二層。 -
★ 「CSV 匯出」是最常見的第二種輸出,而它通常只需要一個獨立的
toCSV()函式。 -
★ 第一步要是純函式,這樣「金額為負要標紅」可以用斷言測試而不必解析 HTML。
-
★ 兩層都要維護:加一個欄位要改三處。
-
除錯多一層:「為什麼沒標紅」要查兩個地方。
🔗 相關工具
- 樣板視圖 Template View(單步方案;可作為兩步驟視圖第二步的實作)
- 轉換視圖 Transform View(常用來實作其兩個轉換步驟)
- 模型-視圖-控制器 Model View Controller(兩步驟視圖是其視圖角色的一種實作)
- 企業應用架構模式
- 樣板視圖 —— ★ 多數情況的正確選擇
- 轉換視圖 —— 可作為兩步驟的任一步
- 資料傳輸物件 —— 第一步的輸出
- 拆分階段 —— 同一想法的一般形式
- 模型-視圖-控制器 —— View 的責任邊界