Skip to content

路由系统 ​

@Page 宏 ​

编译期注册路由:

cangjie
// 基础用法
@Page["/showcase"]
class ShowcasePage <: Component { ... }

// 完整参数
@Page["/admin", "管理后台", adminGuard, sidebarLayout]
class AdminPage <: Component { ... }

@Page 接收 4 个参数:

参数类型必需说明
pathString是路由路径(精确匹配)
titleString否页面标题
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 等坑)见 路由系统 → 把页面放进子包。

基于 MIT 协议开源