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

第三章:路由与页面导航

3.1 路由基础

Go-App 提供了一套声明式的路由系统,支持:

  • 静态路由:如 /, /about, /contact
  • 动态路由:如 /user/{id}, /post/{slug}
  • 嵌套路由:页面内的子路由
  • 重定向:URL 跳转

基本路由配置

func main() {
    // 静态路由
    app.Route("/", func() app.Composer { return &home{} })
    app.Route("/about", func() app.Composer { return &about{} })
    app.Route("/contact", func() app.Composer { return &contact{} })
    
    // 动态路由 - 使用占位符
    app.Route("/user/{id}", func() app.Composer { return &userProfile{} })
    app.Route("/post/{slug}", func() app.Composer { return &blogPost{} })
    
    app.RunWhenOnBrowser()
    
    http.Handle("/", &app.Handler{
        Name: "My App",
    })
    
    http.ListenAndServe(":8000", nil)
}

3.2 获取路由参数

组件可以通过 app.Context 获取当前路由信息。

使用 URL 参数

type userProfile struct {
    app.Compo
    userID string
}

func (u *userProfile) OnMount(ctx app.Context) {
    // 从 URL 获取参数
    u.userID = ctx.Param("id")
    
    // 现在可以使用 userID 加载用户数据
    fmt.Println("当前用户 ID:", u.userID)
}

func (u *userProfile) Render() app.UI {
    return app.Div().Body(
        app.H1().Textf("用户资料: %s", u.userID),
        app.P().Text("这里是用户详细信息..."),
    )
}

查询参数

func (c *myCompo) OnMount(ctx app.Context) {
    // 获取查询参数 ?search=golang&page=2
    search := ctx.QueryParam("search")
    page := ctx.QueryParam("page")
    
    fmt.Printf("搜索: %s, 页码: %s\n", search, page)
}

3.3 页面导航

程序化导航

// 在事件处理器中导航
func (c *myCompo) onButtonClick(ctx app.Context, e app.Event) {
    // 导航到新页面
    ctx.Navigate("/user/123")
}

// 带查询参数的导航
func (c *myCompo) onSearch(ctx app.Context, e app.Event) {
    query := "golang"
    ctx.Navigatef("/search?q=%s", url.QueryEscape(query))
}

// 返回上一页
func (c *myCompo) onGoBack(ctx app.Context, e app.Event) {
    ctx.Navigate("/")  // 或者使用浏览器历史 API
}

使用链接导航

func (c *myCompo) Render() app.UI {
    return app.Nav().Body(
        // 普通链接(整页刷新)
        app.A().
            Href("https://external-site.com").
            Text("外部链接"),
        
        // 路由链接(SPA 方式,无刷新)
        app.RouterLink("/about").
            Text("关于我们"),
        
        // 带样式的链接
        app.RouterLink("/contact").
            Class("nav-link").
            Text("联系我们"),
    )
}

3.4 布局组件

创建布局系统

// layout.go - 布局组件
type layout struct {
    app.Compo
    Title string
}

func (l *layout) Render() app.UI {
    return app.Div().Class("layout").Body(
        // 导航栏
        l.renderNavbar(),
        
        // 主内容区(子组件会渲染在这里)
        app.Main().Class("main-content").Body(
            l.renderContent(),
        ),
        
        // 页脚
        l.renderFooter(),
    )
}

func (l *layout) renderNavbar() app.UI {
    return app.Nav().Class("navbar").Body(
        app.Div().Class("nav-brand").Body(
            app.RouterLink("/").Text("My App"),
        ),
        app.Ul().Class("nav-menu").Body(
            app.Li().Body(app.RouterLink("/").Text("首页")),
            app.Li().Body(app.RouterLink("/about").Text("关于")),
            app.Li().Body(app.RouterLink("/contact").Text("联系")),
        ),
    )
}

func (l *layout) renderFooter() app.UI {
    return app.Footer().Class("footer").Body(
        app.P().Text("© 2026 My App. All rights reserved."),
    )
}

func (l *layout) renderContent() app.UI {
    // 子组件应该在这里渲染
    return app.Text("内容占位")
}

使用布局包裹页面

// 实际页面组件嵌套使用
type homePage struct {
    app.Compo
}

func (h *homePage) Render() app.UI {
    return &layout{
        Title: "首页",
    }.Body(
        // 页面具体内容
        app.H1().Text("欢迎来到首页"),
        app.P().Text("这是首页的内容..."),
    )
}

3.5 路由守卫

认证检查

type protectedPage struct {
    app.Compo
    isAuthenticated bool
}

func (p *protectedPage) OnMount(ctx app.Context) {
    // 检查用户是否已登录
    if !p.isAuthenticated {
        // 未登录,重定向到登录页
        ctx.Navigate("/login")
        return
    }
}

func (p *protectedPage) Render() app.UI {
    // 只有在认证通过后才渲染
    if !p.isAuthenticated {
        return app.Div().Text("重定向中...")
    }
    
    return app.Div().Body(
        app.H1().Text("受保护的页面"),
        app.P().Text("只有登录用户才能看到此内容"),
    )
}

权限控制

type adminPage struct {
    app.Compo
    userRole string
}

func (a *adminPage) OnMount(ctx app.Context) {
    // 检查权限
    if a.userRole != "admin" {
        // 无权限,显示错误或重定向
        ctx.Navigate("/unauthorized")
        return
    }
}

3.6 导航状态管理

当前页面高亮

type navbar struct {
    app.Compo
}

func (n *navbar) Render() app.UI {
    return app.Nav().Class("navbar").Body(
        app.Ul().Class("nav-menu").Body(
            n.navItem("/", "首页"),
            n.navItem("/about", "关于"),
            n.navItem("/contact", "联系"),
            n.navItem("/user/profile", "我的"),
        ),
    )
}

func (n *navbar) navItem(path, label string) app.UI {
    // 获取当前路径
    currentPath := app.Window().URL().Path
    
    // 判断是否为当前页面
    isActive := currentPath == path
    
    return app.Li().Body(
        app.RouterLink(path).
            Class(func() string {
                if isActive {
                    return "nav-link active"
                }
                return "nav-link"
            }()).
            Text(label),
    )
}

3.7 动态路由匹配

多段路径参数

// 路由: /shop/{category}/{product-id}
type productPage struct {
    app.Compo
    category  string
    productID string
}

func (p *productPage) OnMount(ctx app.Context) {
    p.category = ctx.Param("category")
    p.productID = ctx.Param("product-id")
    
    // 加载产品数据
    p.loadProduct()
}

可选参数

// 处理可选的查询参数
func (c *searchPage) OnMount(ctx app.Context) {
    // /search?q=golang&category=all&sort=date
    params := struct {
        Query    string
        Category string
        Sort     string
        Page     int
    }{
        Query:    ctx.QueryParam("q"),
        Category: ctx.QueryParam("category"),
        Sort:     ctx.QueryParam("sort"),
    }
    
    // 设置默认值
    if params.Category == "" {
        params.Category = "all"
    }
    if params.Sort == "" {
        params.Sort = "relevance"
    }
    
    // 页码转换
    if pageStr := ctx.QueryParam("page"); pageStr != "" {
        params.Page, _ = strconv.Atoi(pageStr)
    } else {
        params.Page = 1
    }
}

3.8 浏览器历史管理

监听 URL 变化

type urlWatcher struct {
    app.Compo
    currentURL string
}

func (u *urlWatcher) OnMount(ctx app.Context) {
    // 获取当前 URL
    u.currentURL = app.Window().URL().String()
    
    // 监听 URL 变化
    ctx.Handle("url-change", func(e app.Event) {
        u.currentURL = app.Window().URL().String()
        u.Update()
    })
}

func (u *urlWatcher) Render() app.UI {
    return app.Div().Body(
        app.P().Textf("当前 URL: %s", u.currentURL),
    )
}

3.9 完整的路由示例

package main

import (
	"log"
	"net/http"

	"github.com/maxence-charriere/go-app/v10/pkg/app"
)

// 首页
type home struct{ app.Compo }

func (h *home) Render() app.UI {
	return app.Div().Body(
		app.H1().Text("首页"),
		app.P().Text("欢迎来到 Go-App 示例应用"),
		app.RouterLink("/about").Text("了解更多 →"),
	)
}

// 关于页
type about struct{ app.Compo }

func (a *about) Render() app.UI {
	return app.Div().Body(
		app.H1().Text("关于"),
		app.P().Text("这是一个使用 Go-App 构建的示例应用。"),
		app.RouterLink("/").Text("← 返回首页"),
	)
}

// 用户资料页
type userProfile struct {
	app.Compo
	userID string
}

func (u *userProfile) OnMount(ctx app.Context) {
	u.userID = ctx.Param("id")
}

func (u *userProfile) Render() app.UI {
	return app.Div().Body(
		app.H1().Textf("用户 %s 的资料", u.userID),
		app.P().Text("这里是用户的详细信息..."),
		app.Button().
			Text("查看用户文章").
			OnClick(func(ctx app.Context, e app.Event) {
				ctx.Navigatef("/user/%s/posts", u.userID)
			}),
	)
}

// 404 页面
type notFound struct{ app.Compo }

func (n *notFound) Render() app.UI {
	return app.Div().Body(
		app.H1().Text("404 - 页面未找到"),
		app.P().Text("抱歉,您访问的页面不存在。"),
		app.RouterLink("/").Text("返回首页"),
	)
}

func main() {
	// 路由配置
	app.Route("/", func() app.Composer { return &home{} })
	app.Route("/about", func() app.Composer { return &about{} })
	app.Route("/user/{id}", func() app.Composer { return &userProfile{} })
	
	// 运行应用
	app.RunWhenOnBrowser()

	// HTTP 服务器
	http.Handle("/", &app.Handler{
		Name:        "Routing Demo",
		Description: "Go-App 路由示例",
		Title:       "路由示例",
	})

	log.Println("服务器启动在 http://localhost:8000")
	if err := http.ListenAndServe(":8000", nil); err != nil {
		log.Fatal(err)
	}
}

3.10 本章小结

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

  1. ✅ Go-App 的路由系统基础
  2. ✅ 如何配置静态和动态路由
  3. ✅ 从 URL 获取参数和查询字符串
  4. ✅ 程序化导航和链接跳转
  5. ✅ 创建布局组件
  6. ✅ 实现路由守卫和认证检查
  7. ✅ 导航状态管理和页面高亮
下一步:学习状态管理与事件处理。

练习

  1. 创建一个包含导航栏的多页面应用(首页、关于、联系)
  2. 实现一个用户资料页,URL 为 /user/{id}
  3. 添加一个搜索页,支持查询参数 /search?q=keyword
  4. 创建一个需要登录才能访问的受保护页面

下一章:状态管理与事件处理

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