第三章:路由与页面导航
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 本章小结
在这一章中,我们学习了:
- ✅ Go-App 的路由系统基础
- ✅ 如何配置静态和动态路由
- ✅ 从 URL 获取参数和查询字符串
- ✅ 程序化导航和链接跳转
- ✅ 创建布局组件
- ✅ 实现路由守卫和认证检查
- ✅ 导航状态管理和页面高亮
下一步:学习状态管理与事件处理。
练习
- 创建一个包含导航栏的多页面应用(首页、关于、联系)
- 实现一个用户资料页,URL 为
/user/{id} - 添加一个搜索页,支持查询参数
/search?q=keyword - 创建一个需要登录才能访问的受保护页面
下一章:状态管理与事件处理
#教程 #Go #GoApp #PWA #WebAssembly #小凯