You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言:如何实现HTML元素链式调用并精简重复代码

解决方案:泛型+CRTP实现可组合的HTML元素链式调用

要解决重复编写通用方法的问题,我们可以利用Go的泛型特性结合奇异递归模板模式(CRTP),让通用方法仅实现一次,同时保证链式调用返回具体元素的指针。以下是具体实现步骤:

1. 重构通用方法为泛型基类

定义泛型结构体BaseElement,持有核心的*element实例,实现所有通用属性方法,返回值为具体元素类型*T:

import (
    "fmt"
    "strings"
    "unsafe"
)

// 保留原有的element结构体和内部方法不变
type element struct {
    tagName       string
    hasClosingTag bool
    attributes    []string
    classes       []string
    styles        []string
    elements      []fmt.Stringer
    htmx          *htmx // 假设htmx类型已定义
}

// 泛型基类,T为具体元素类型(如Div、P)
type BaseElement[T any] struct {
    elem *element
}

// 通用方法:设置ID
func (b *BaseElement[T]) Id(id string) *T {
    b.elem.addAttribute("id", id)
    return (*T)(unsafe.Pointer(b))
}

// 通用方法:添加类名
func (b *BaseElement[T]) Class(class ...string) *T {
    b.elem.addClasses(class...)
    return (*T)(unsafe.Pointer(b))
}

// 通用方法:添加样式
func (b *BaseElement[T]) Style(style ...string) *T {
    b.elem.addStyles(style...)
    return (*T)(unsafe.Pointer(b))
}

// 通用方法:添加子元素
func (b *BaseElement[T]) Content(el ...fmt.Stringer) *T {
    b.elem.addElements(el...)
    return (*T)(unsafe.Pointer(b))
}

// 通用方法:设置hidden属性
func (b *BaseElement[T]) Hidden() *T {
    b.elem.addAttribute("hidden")
    return (*T)(unsafe.Pointer(b))
}

// 实现fmt.Stringer接口,复用element的String方法
func (b *BaseElement[T]) String() string {
    return b.elem.String()
}

// 保留原element的所有内部方法(addAttribute、getAttributes等)不变

2. 定义具体元素结构体

每个HTML元素只需嵌入BaseElement并传入自身作为类型参数,按需实现专属方法:

// Div元素结构体,嵌入BaseElement[Div]
type Div struct {
    BaseElement[Div]
}

// Div的构造函数
func Div() *Div {
    d := &Div{}
    d.elem = newElement("div", true)
    return d
}

// Div的专属方法:设置lang属性
func (d *Div) Lang(lang string) *Div {
    d.elem.addAttribute("lang", lang)
    return d
}

// P元素结构体,仅拥有通用方法
type P struct {
    BaseElement[P]
}

func P() *P {
    p := &P{}
    p.elem = newElement("p", true)
    return p
}

// Button元素结构体,拥有专属方法Disabled
type Button struct {
    BaseElement[Button]
}

func Button() *Button {
    btn := &Button{}
    btn.elem = newElement("button", true)
    return btn
}

func (btn *Button) Disabled() *Button {
    btn.elem.addAttribute("disabled")
    return btn
}

3. 链式调用示例

通用方法和专属方法可无缝衔接,保持原有的调用风格:

func main() {
    html := Div().Id("div-one").Class("class-one", "class-two").Lang("en").Content(
        P().Content(InnerHtml("Hello World")),
        Button().Id("submit-btn").Disabled().Content(InnerHtml("Submit")),
    )
    fmt.Println(html.String())
}

// 实现fmt.Stringer的文本元素
type InnerHtml string

func (i InnerHtml) String() string {
    return string(i)
}

关键说明

  • 泛型CRTP的作用:通过将具体元素类型T传入BaseElement,让通用方法返回*T,保证链式调用始终返回当前元素指针,支持后续调用专属方法。
  • unsafe.Pointer的安全性:由于BaseElement[T]是具体元素结构体的第一个嵌入字段,两者内存地址完全重合,因此转换是安全的。若不想使用unsafe,可为每个元素添加Self() *T方法返回自身,但会增加少量重复代码。
  • 代码复用:所有通用属性方法仅需实现一次,新增元素只需定义结构体、构造函数和专属方法即可,大幅减少重复代码。

内容的提问来源于stack exchange,提问作者Fabián Alejandro Pallares

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 01:42:02