路由系统
@Page 宏
编译期注册路由:
cangjie
// 基础用法
@Page["/showcase"]
class ShowcasePage <: Component { ... }
// 完整参数
@Page["/admin", "管理后台", adminGuard, sidebarLayout]
class AdminPage <: Component { ... }@Page 接收 4 个参数:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
| path | String | 是 | 路由路径(精确匹配) |
| title | String | 否 | 页面标题 |
| guard | (HashMap<String,String>) -> Bool | 否 | 路由守卫函数 |
| layout | (IComponent) -> IComponent | 否 | 布局函数 |
编译期展开为:
cangjie
// @Page["/showcase"]
// ↓ 编译后追加:
let __reg_ShowcasePage = RouteRegistry.global().register(
"/showcase", { => ShowcasePage() }
)RouteRegistry
单例模式,使用 HashMap 精确路径匹配:
cangjie
let reg = RouteRegistry.global()
reg.register("/counter", { => CounterPage() })
reg.register("/admin", { => AdminPage() }, "管理后台", adminGuard)
reg.register("/profile", { => ProfilePage() }, "个人中心", null, sidebarLayout)创建页面
cangjie
match (reg.create("/order/123")) {
case Some(page) => render(page)
case None => notFound()
}守卫
守卫是函数,接收会话上下文返回布尔值:
cangjie
func adminGuard(ctx: HashMap<String, String>): Bool {
match (ctx.get("role")) {
case Some(r) => r == "admin"
case None => false
}
}
reg.register("/admin", { => AdminPage() }, "管理后台", adminGuard)
reg.checkGuard("/admin", session.context)布局
cangjie
let sidebarLayout: LayoutFn = { page =>
div([sidebar(), page])
}
reg.register("/dashboard", { => DashboardPage() }, "仪表盘", null, sidebarLayout)
match (reg.getLayout("/dashboard")) {
case Some(layout) => layout(page)
case None => page
}页面标题
cangjie
let title = reg.getTitle("/about") // → "关于我们"RouteEntry 手动路径匹配
对于动态路径参数,使用 RouteEntry 手动解析:
cangjie
let entry = RouteEntry("/user/[id]/post/[postId]")
match (entry.doMatch("/user/123/post/456")) {
case Some(params) =>
let userId = params.get("id") // "123"
let postId = params.get("postId") // "456"
case None => // 不匹配
}匹配规则:
- 字面段必须完全匹配
[name]段匹配任意值并捕获- 段数必须一致
路由导航
cangjie
ctx.route.push("/about")
// 或
Router.push("/about")导航流程
handleNavigate → state.page.onUnmount()
→ RouteRegistry.create(path)
→ applyNav → newPage.onMount()
→ renderWithScope → fullTree⚠️ 页面放进子包时:必须让根包 import 它
@Page 展开出的注册代码落在定义它的那个包里,而 cjpm 只把从根包 import 可达的包 链进产物。子包没被任何包 import 时,它不进链接图、包初始化不执行,页面静默 404: 编译成功、无任何警告、二进制里连该页面的符号都没有。
cangjie
// src/main.cj —— 拆包后必须显式 import(纯 import 就够,无需调用其函数)
import myapp.ui.admin.* // ← 少了这行,/admin 就 404
import myapp.ui.public.*- 子包多 / 频繁加页面时,改用脚本从源码扫描
@Page自动生成聚合 import(附--check供 CI 校验); - 不要用
nm/ 符号表判断是否注册成功 —— 未链接的包符号根本不在二进制里;即使强塞入符号, 包初始化依然不执行。判据取运行时路由表或 HTTP 探测。
完整说明(含可直接使用的生成脚本、路由守卫、中间层目录必须有 .cj 等坑)见 路由系统 → 把页面放进子包。