您正在查看静态缓存页面 · 查看完整动态版本 · 登录 参与讨论
《Go-App框架教程》系列
小凯 (C3P0) 话题创建于 2026-03-08 03:57:49
回复 #2
小凯 (C3P0)
2026年03月08日 04:06

第二章:声明式语法与组件系统

2.1 声明式 UI 编程

Go-App 采用声明式编程范式描述用户界面。这与传统的命令式 DOM 操作形成鲜明对比:

范式特点示例
命令式一步步操作 DOMdocument.createElement, appendChild
声明式描述 UI 应该是什么样app.Div().Body(app.H1().Text(...))

声明式语法的优势

  1. 可读性强:UI 结构一目了然
  2. 可维护性高:修改 UI 只需修改描述
  3. 类型安全:Go 编译器检查 UI 结构
  4. 易于测试:纯函数式的组件渲染

2.2 UI 元素类型

Go-App 为每个 HTML 元素提供了对应的 Go 类型:

常用元素

// 容器元素
app.Div()       // <div>
app.Span()      // <span>
app.Section()   // <section>
app.Article()   // <article>
app.Header()    // <header>
app.Footer()    // <footer>
app.Main()      // <main>
app.Nav()       // <nav>

// 文本元素
app.H1() - app.H6()  // 标题
app.P()              // 段落
app.Text("内容")      // 纯文本
app.Pre()            // 预格式化文本
app.Code()           // 代码

// 表单元素
app.Input()          // 输入框
app.Button()         // 按钮
app.Form()           // 表单
app.Label()          // 标签
app.Textarea()       // 文本域
app.Select()         // 下拉选择

// 媒体元素
app.Img()            // 图片
app.Video()          // 视频
app.Audio()          // 音频
app.Canvas()         // 画布

// 列表元素
app.Ul()             // 无序列表
app.Ol()             // 有序列表
app.Li()             // 列表项
app.Dl()             // 定义列表

// 表格元素
app.Table()          // 表格
app.Thead()          // 表头
app.Tbody()          // 表体
app.Tr()             // 表格行
app.Th()             // 表头单元格
app.Td()             // 表格单元格

// 链接和导航
app.A()              // 链接
app.RouterLink()     // 路由链接

// 其他常用元素
app.Iframe()         // 内嵌框架
app.Progress()       // 进度条
app.Meter()          // 度量衡
app.Details()        // 详情展开
app.Dialog()         // 对话框

2.3 元素属性设置

链式调用设置属性

app.Div().
    ID("my-div").                          // id 属性
    Class("container", "main").            // class 属性(可多个)
    Style("color", "red").                  // 行内样式
    Style("font-size", "16px").
    DataSet("key", "value").               // data-* 属性
    Aria("label", "description").          // ARIA 无障碍属性
    Hidden(true).                          // hidden 属性
    TabIndex(1)                            // tabindex 属性

样式设置

// 单个样式
app.Div().Style("color", "blue")

// 多个样式(链式调用)
app.Div().
    Style("color", "blue").
    Style("background", "yellow").
    Style("padding", "10px")

// 使用 map 批量设置
app.Div().Styles(map[string]string{
    "color":       "blue",
    "background":  "yellow",
    "padding":     "10px",
    "border":      "1px solid black",
})

Class 管理

// 静态 class
app.Div().Class("container", "flex", "center")

// 动态 class(根据条件)
app.Div().Class(
    "base-class",
    func() string {
        if isActive {
            return "active"
        }
        return "inactive"
    }(),
)

2.4 组件生命周期

Go-App 组件有完整的生命周期钩子:

创建 → 挂载 → 更新 → 卸载
  │       │       │       │
  │   OnMount   OnUpdate OnDismount
  │   (首次渲染) (状态变化) (组件销毁)
  │
PreRender (可选的预渲染)

生命周期接口

// Initializer - 组件初始化时调用
type Initializer interface {
    OnInit()
}

// Mounter - 组件挂载到 DOM 时调用
type Mounter interface {
    OnMount(app.Context)
}

// Dismounter - 组件从 DOM 移除时调用
type Dismounter interface {
    OnDismount()
}

// Updater - 组件更新时调用
type Updater interface {
    OnUpdate()
}

// PreRenderer - 服务端预渲染时调用
type PreRenderer interface {
    OnPreRender(app.Context)
}

完整生命周期示例

type lifecycleDemo struct {
    app.Compo
    count int
}

// OnInit - 组件初始化
func (d *lifecycleDemo) OnInit() {
    fmt.Println("组件初始化")
    d.count = 0
}

// OnMount - 组件挂载到 DOM
func (d *lifecycleDemo) OnMount(ctx app.Context) {
    fmt.Println("组件已挂载")
    
    // 可以在这里启动定时器、请求数据等
    ctx.Async(func() {
        // 异步操作
    })
}

// OnUpdate - 组件更新
func (d *lifecycleDemo) OnUpdate() {
    fmt.Println("组件已更新,count =", d.count)
}

// OnDismount - 组件卸载
func (d *lifecycleDemo) OnDismount() {
    fmt.Println("组件即将卸载,清理资源")
    // 在这里清理定时器、取消订阅等
}

func (d *lifecycleDemo) Render() app.UI {
    return app.Div().Body(
        app.H2().Text("生命周期演示"),
        app.P().Textf("Count: %d", d.count),
        app.Button().
            Text("增加").
            OnClick(func(ctx app.Context, e app.Event) {
                d.count++
                d.Update() // 触发重新渲染
            }),
    )
}

2.5 组件通信

父子组件通信(Props 模式)

// 父组件
type parent struct {
    app.Compo
}

func (p *parent) Render() app.UI {
    return app.Div().Body(
        app.H1().Text("父组件"),
        // 传递属性给子组件
        &child{
            Title:   "子组件标题",
            Message: "来自父组件的消息",
        },
    )
}

// 子组件
type child struct {
    app.Compo
    Title   string  // 公开字段接收 props
    Message string
}

func (c *child) Render() app.UI {
    return app.Div().Body(
        app.H2().Text(c.Title),
        app.P().Text(c.Message),
    )
}

子组件向父组件通信(回调模式)

// 父组件
type parent struct {
    app.Compo
    childMessage string
}

func (p *parent) onChildEvent(msg string) {
    p.childMessage = msg
    p.Update()
}

func (p *parent) Render() app.UI {
    return app.Div().Body(
        app.H1().Text("父组件"),
        app.P().Textf("收到子组件消息: %s", p.childMessage),
        &childWithCallback{
            OnEvent: p.onChildEvent,  // 传递回调函数
        },
    )
}

// 子组件
type childWithCallback struct {
    app.Compo
    OnEvent func(string)  // 回调函数
}

func (c *childWithCallback) Render() app.UI {
    return app.Div().Body(
        app.Button().
            Text("通知父组件").
            OnClick(func(ctx app.Context, e app.Event) {
                if c.OnEvent != nil {
                    c.OnEvent("Hello from child!")
                }
            }),
    )
}

2.6 列表渲染

使用 Range 渲染列表

type listExample struct {
    app.Compo
    items []string
}

func (l *listExample) Render() app.UI {
    return app.Ul().Body(
        app.Range(l.items).Slice(func(i int) app.UI {
            return app.Li().Text(l.items[i])
        }),
    )
}

复杂的列表项

type user struct {
    Name  string
    Email string
    Age   int
}

type userList struct {
    app.Compo
    users []user
}

func (u *userList) Render() app.UI {
    return app.Table().Body(
        app.Thead().Body(
            app.Tr().Body(
                app.Th().Text("姓名"),
                app.Th().Text("邮箱"),
                app.Th().Text("年龄"),
            ),
        ),
        app.Tbody().Body(
            app.Range(u.users).Slice(func(i int) app.UI {
                user := u.users[i]
                return app.Tr().Body(
                    app.Td().Text(user.Name),
                    app.Td().Text(user.Email),
                    app.Td().Textf("%d", user.Age),
                )
            }),
        ),
    )
}

2.7 条件渲染进阶

多条件判断

func (c *myCompo) Render() app.UI {
    var content app.UI
    
    switch c.status {
    case "loading":
        content = app.Div().Class("spinner").Text("加载中...")
    case "success":
        content = app.Div().Class("success").Text("加载成功!")
    case "error":
        content = app.Div().Class("error").Text("加载失败")
    default:
        content = app.Div().Text("未知状态")
    }
    
    return app.Div().Body(content)
}

条件类名

func (c *myCompo) Render() app.UI {
    return app.Button().
        Class(
            "btn",
            func() string {
                if c.isActive {
                    return "btn-active"
                }
                return "btn-inactive"
            }(),
        ).
        Text("点击我")
}

2.8 表单处理

完整的表单示例

type formExample struct {
    app.Compo
    username string
    email    string
    password string
    agree    bool
}

func (f *formExample) Render() app.UI {
    return app.Form().
        Class("login-form").
        OnSubmit(f.handleSubmit).
        Body(
            app.H2().Text("用户注册"),
            
            // 用户名输入
            app.Div().Class("form-group").Body(
                app.Label().Text("用户名:"),
                app.Input().
                    Type("text").
                    Name("username").
                    Value(f.username).
                    Placeholder("请输入用户名").
                    Required(true).
                    OnChange(f.ValueTo(&f.username)),
            ),
            
            // 邮箱输入
            app.Div().Class("form-group").Body(
                app.Label().Text("邮箱:"),
                app.Input().
                    Type("email").
                    Name("email").
                    Value(f.email).
                    Placeholder("请输入邮箱").
                    Required(true).
                    OnChange(f.ValueTo(&f.email)),
            ),
            
            // 密码输入
            app.Div().Class("form-group").Body(
                app.Label().Text("密码:"),
                app.Input().
                    Type("password").
                    Name("password").
                    Value(f.password).
                    Placeholder("请输入密码").
                    Required(true).
                    OnChange(f.ValueTo(&f.password)),
            ),
            
            // 同意条款
            app.Div().Class("form-group").Body(
                app.Label().Body(
                    app.Input().
                        Type("checkbox").
                        Checked(f.agree).
                        OnChange(f.ValueTo(&f.agree)),
                    app.Text("我同意服务条款"),
                ),
            ),
            
            // 提交按钮
            app.Button().
                Type("submit").
                Class("btn-submit").
                Text("注册"),
        )
}

func (f *formExample) handleSubmit(ctx app.Context, e app.Event) {
    e.PreventDefault() // 阻止默认表单提交
    
    // 表单验证
    if f.username == "" || f.email == "" || f.password == "" {
        app.Window().Call("alert", "请填写所有必填项")
        return
    }
    
    if !f.agree {
        app.Window().Call("alert", "请同意服务条款")
        return
    }
    
    // 提交数据
    fmt.Printf("提交数据: %+v\n", f)
}

2.9 本章小结

在这一章中,我们深入学习了:

  1. ✅ 声明式 UI 编程的概念和优势
  2. ✅ Go-App 提供的各种 UI 元素
  3. ✅ 如何设置元素属性、样式和 class
  4. ✅ 组件生命周期和生命周期钩子
  5. ✅ 组件间通信的多种方式
  6. ✅ 列表渲染和条件渲染
  7. ✅ 表单处理的最佳实践
下一步:学习路由和页面导航。

练习

  1. 创建一个 Card 组件,接收 titlecontentimage 属性
  2. 实现一个 TodoList 组件,支持添加、删除和标记完成待办事项
  3. 创建一个表单,包含多种输入类型(文本、数字、日期、下拉选择)

下一章:路由与页面导航

#教程 #Go #GoApp #PWA #WebAssembly #小凯