学习目标
本课解决三个问题:如何定义多页面路由、如何在页面间导航、如何组织嵌套布局和动态参数。
知识点1:启用路由功能
在 Cargo.toml 中添加 router feature:
[dependencies] dioxus = { version = "0.7", features = ["router"] }知识点2:定义路由枚举
用 #[derive(Routable)] 标记一个枚举,每个变体对应一个页面。变体名必须和组件名一致。
usedioxus::prelude::*;// 定义路由枚举#[derive(Routable, Clone, PartialEq)]enumRoute{#[route("/")]Home{},#[route("/about")]About{},#[route("/contact")]Contact{},}// 每个路由对应的组件必须存在#[component]fnHome()->Element{rsx!{h1{"首页"}}}#[component]fnAbout()->Element{rsx!{h1{"关于我们"}}}#[component]fnContact()->Element{rsx!{h1{"联系方式"}}}规则
- 枚举变体名 = 组件名(如 Home {} 对应 fn Home())
- #[route(“/path”)] 指定 URL 路径
- 枚举需要派生 Clone 和 PartialEq
知识点3:渲染路由 — Router
在根组件中用 Router:: {} 启动路由:
#[component]fnApp()->Element{rsx!{Router::<Route>{}}}fnmain(){dioxus::launch(App);}Router 会根据浏览器当前 URL 自动匹配并渲染对应的组件。
知识点4:页面导航 — Link 组件
用 Link 组件在页面间跳转,它不会触发整页刷新:
#[component]fnNavBar()->Element{rsx!{nav{style:"display: flex; gap: 16px; padding: 16px; background: #f5f5f5;",Link{to:Route::Home{},"首页"}Link{to:Route::About{},"关于"}Link{to:Route::Contact{},"联系"}}}}Link 的 to 属性接收一个路由枚举值,类型安全——如果路由不存在,编译就会报错。
知识点5:布局组件 — #[layout] + Outlet
多个页面共享的导航栏、页脚等,用布局组件包裹。布局组件内部用 Outlet:: {} 标记子路由的渲染位置。
#[component]fnLayout()->Element{rsx!{div{// 导航栏(所有页面共享)nav{style:"display: flex; gap: 16px; padding: 16px; background: #333; color: white;",Link{to:Route::Home{},style:"color: white;","首页"}Link{to:Route::About{},style:"color: white;","关于"}Link{to:Route::Contact{},style:"color: white;","联系"}}// Outlet:子路由的页面内容会渲染在这里main{style:"padding: 20px;",Outlet::<Route>{}}// 页脚(所有页面共享)footer{style:"padding: 16px; background: #f5f5f5; text-align: center;","© 2026 我的网站"}}}}在路由枚举中用 #[layout] 和 #[end_layout] 标记哪些路由使用这个布局:
#[derive(Routable, Clone, PartialEq)]enumRoute{#[layout(Layout)]#[route("/")]Home{},#[route("/about")]About{},#[route("/contact")]Contact{},#[end_layout]}渲染结构:
Router
└── Layout
├── nav(导航栏)
├── Outlet
│ └── Home / About / Contact(根据 URL 切换)
└── footer(页脚)
知识点6:动态路由参数
URL 中的动态部分用 :参数名 表示,对应组件的参数:
#[derive(Routable, Clone, PartialEq)]enumRoute{#[route("/")]Home{},// :user_id 是动态参数#[route("/user/:user_id")]UserProfile{user_id:u32},// :category 和 :post_id 都是动态参数#[route("/blog/:category/:post_id")]BlogPost{category:String,post_id:u32},}// 组件参数名必须和路由参数名一致#[component]fnUserProfile(user_id:u32)->Element{rsx!{h1{"用户 #{user_id} 的个人主页"}}}#[component]fnBlogPost(category:String,post_id:u32)->Element{rsx!{h1{"分类:{category},文章 #{post_id}"}}}类型安全
- 路由参数会自动解析为组件参数的类型
- 如果 URL 中的参数无法解析(如 /user/abc 解析为 u32 失败),路由不会匹配
- 支持的类型:String、u32、i32、usize 等实现了 FromStr 的类型
知识点7:嵌套路由 — #[nest]
用 #[nest] 给一组路由添加公共 URL 前缀:
#[derive(Routable, Clone, PartialEq)]enumRoute{#[route("/")]Home{},#[nest("/blog")]#[layout(BlogLayout)]#[route("/")]BlogList{},#[route("/:post_id")]BlogPost{post_id:u32},#[end_layout]#[end_nest]}#[component]fnBlogLayout()->Element{rsx!{div{h2{"📝 博客"}Link{to:Route::BlogList{},"← 返回博客列表"}hr{}// 子路由渲染在这里Outlet::<Route>{}}}}#[component]fnBlogList()->Element{rsx!{ul{li{Link{to:Route::BlogPost{post_id:1},"第一篇博客"}}li{Link{to:Route::BlogPost{post_id:2},"第二篇博客"}}li{Link{to:Route::BlogPost{post_id:3},"第三篇博客"}}}}}#[component]fnBlogPost(post_id:u32)->Element{rsx!{article{h3{"博客文章 #{post_id}"}p{"这是第 {post_id} 篇博客的内容..."}}}}URL 对应关系:
URL 匹配路由
/ Home
/blog/ BlogList(在 BlogLayout 内)
/blog/1 BlogPost { post_id: 1 }(在 BlogLayout 内)
/blog/2 BlogPost { post_id: 2 }(在 BlogLayout 内)
知识点8:编程式导航
除了 Link 组件,还可以在代码中主动跳转:
#[component]fnLoginButton()->Element{letmutnavigator=use_navigator();rsx!{button{onclick:move|_|{// 登录成功后跳转到首页navigator.push(Route::Home{});},"登录"}}}use_navigator() 提供的方法:
方法 说明
navigator.push(route) 跳转到新页面(加入历史记录)
navigator.replace(route) 替换当前页面(不加入历史记录)
navigator.go_back() 后退到上一页
navigator.go_forward() 前进到下一页
知识点9:获取当前路由
在组件中读取当前路由信息:
#[component]fnCurrentRouteDisplay()->Element{// 获取当前路由letcurrent_route=use_route::<Route>();rsx!{p{"当前页面:{current_route}"}}}知识点10:404 页面 — 通配路由
用 /:…segments 捕获所有未匹配的路径:
#[derive(Routable, Clone, PartialEq)]enumRoute{#[route("/")]Home{},#[route("/about")]About{},// 通配路由:匹配所有未定义的路径#[route("/:..segments")]PageNotFound{segments:Vec<String>},}#[component]fnPageNotFound(segments:Vec<String>)->Element{rsx!{div{style:"text-align: center; padding: 40px;",h1{"404"}p{"页面未找到"}p{"路径:/{segments.join("/")}"}Link{to:Route::Home{},"返回首页"}}}}注意:通配路由必须放在枚举的最后。
知识点11:查询参数
用 ?参数名 定义查询参数(URL 中 ? 后面的部分):
#[derive(Routable, Clone, PartialEq)]enumRoute{#[route("/")]Home{},// 查询参数:/search?q=hello&page=2#[route("/search?:q&page")]Search{q:String,page:u32},}#[component]fnSearch(q:String,page:u32)->Element{rsx!{div{h2{"搜索结果:"{q}""}p{"第 {page} 页"}}}}核心规则
概念 说明
#[derive(Routable)] 标记路由枚举
#[route(“/path”)] 定义 URL 路径
Router:: {} 渲染路由
Link { to: Route::Xxx {} } 声明式导航(不刷新页面)
#[layout(Comp)] / #[end_layout] 布局组件包裹子路由
Outlet:: {} 布局中标记子路由渲染位置
#[nest(“/prefix”)] / #[end_nest] 路由 URL 前缀
:param 动态路由参数
/:…segments 通配路由(404 页面)
?param 查询参数
use_navigator() 编程式导航
use_route::() 获取当前路由
动手试试
补全下面的代码:
usedioxus::prelude::*;fnmain(){dioxus::launch(App);}// === 题目:个人博客路由系统 ===// 要求:// - 定义以下路由:// - "/" → Home 组件(首页)// - "/about" → About 组件(关于页面)// - "/blog" → BlogList 组件(博客列表,在 BlogLayout 布局内)// - "/blog/:post_id" → BlogPost 组件(博客详情,在 BlogLayout 布局内)// - "/user/:username" → UserProfile 组件(用户主页)// - 通配路由 → PageNotFound 组件(404 页面)//// - Layout 布局组件(包裹所有页面):// - 顶部导航栏,包含:首页、关于、博客 三个 Link// - 中间是 Outlet// - 底部页脚显示 "© 2026 我的博客"//// - BlogLayout 布局组件(包裹博客相关页面):// - 显示 "📝 博客" 标题// - 一个"返回列表"的 Link// - Outlet 渲染子路由//// - Home 组件:显示 "欢迎来到我的博客" 和简介// - About 组件:显示 "关于我" 的信息// - BlogList 组件:// - 显示 3 篇博客的标题(用 Link 跳转到对应 BlogPost)// - 每篇博客有标题和摘要// - BlogPost 组件:// - 接收 post_id: u32 参数// - 根据 post_id 显示不同的博客内容(用 match 模拟)// - 如果 post_id > 3,显示 "文章不存在"// - UserProfile 组件:// - 接收 username: String 参数// - 显示 "用户 {username} 的主页"// - PageNotFound 组件:// - 显示 "404 页面未找到"// - 提供返回首页的 Link//// - App 组件:渲染 Router::<Route> {}#[component]fnApp()->Element{// 补全todo!()}// 补全路由枚举// #[derive(Routable, Clone, PartialEq)]// enum Route {// // 补全// }// 补全所有组件// fn Layout() -> Element { todo!() }// fn BlogLayout() -> Element { todo!() }// fn Home() -> Element { todo!() }// fn About() -> Element { todo!() }// fn BlogList() -> Element { todo!() }// fn BlogPost(post_id: u32) -> Element { todo!() }// fn UserProfile(username: String) -> Element { todo!() }// fn PageNotFound(segments: Vec<String>) -> Element { todo!() }期望效果
- 访问 /:显示首页,顶部有导航栏,底部有页脚
- 点击导航栏"博客":跳转到 /blog,显示博客列表,在博客布局内
- 点击某篇博客:跳转到 /blog/1,显示博客详情,仍有博客布局
- 访问 /user/alice:显示 “用户 alice 的主页”
- 访问 /xyz:显示 404 页面
写好了贴过来,没问题就进入第8课:异步数据获取与资源管理 👋
贴代码
Dioxus‑0.7 路由完整参考答案(Routable、嵌套布局、路由参数、404)
Cargo.toml
[package] name = "dioxus-blog-router" version = "0.1.0" edition = "2021" [dependencies] dioxus = { version = "0.7", features = ["router"] }main.rs 完整代码
usedioxus::prelude::*;usedioxus_router::prelude::*;fnmain(){dioxus::launch(App);}#[component]fnApp()->Element{rsx!{Router::<Route>{}}}// ========== 路由枚举定义 ==========#[derive(Routable, Clone, PartialEq)]enumRoute{#[route("/")]Home{},#[route("/about")]About{},#[layout(BlogLayout)]#[route("/blog")]BlogList{},#[layout(BlogLayout)]#[route("/blog/:post_id")]BlogPost{post_id:u32},#[route("/user/:username")]UserProfile{username:String},#[route("/:..segments")]PageNotFound{segments:Vec<String>},}// ========== 全局布局 Layout(导航 + Outlet + 页脚) ==========#[component]fnLayout()->Element{rsx!{div{style:"max-width:800px;margin:0 auto;padding:20px;font-family:sans-serif;",nav{style:"margin-bottom:20px;border-bottom:1px solid #ccc;padding-bottom:10px;",Link{to:Route::Home{},style:"margin-right:12px;","首页"}Link{to:Route::About{},style:"margin-right:12px;","关于"}Link{to:Route::BlogList{},"博客"}}Outlet::<Route>{}footer{style:"margin-top:40px;border-top:1px solid #ccc;padding-top:10px;color:#666;","© 2026 我的博客"}}}}// ========== 博客嵌套布局 BlogLayout ==========#[component]fnBlogLayout()->Element{rsx!{div{style:"padding:16px;border:1px solid #ddd;border-radius:8px;",h2{"📝 博客"}Link{to:Route::BlogList{},style:"display:inline-block;margin-bottom:16px;","← 返回列表"}Outlet::<Route>{}}}}// ========== 首页 Home ==========#[component]fnHome()->Element{rsx!{Layout{h1{"欢迎来到我的博客"}p{"这里分享Rust、Dioxus前端技术学习笔记。"}}}}// ========== 关于页面 About ==========#[component]fnAbout()->Element{rsx!{Layout{h1{"关于我"}p{"一名Rust全栈爱好者,正在学习Dioxus跨端开发。"}p{"目标:使用Dioxus开发桌面端、Web端工具软件。"}}}}// ========== 博客列表 BlogList ==========#[component]fnBlogList()->Element{rsx!{div{h3{"文章列表"}div{style:"margin:12px 0;padding:8px;border:1px solid #eee;border-radius:4px;",Link{to:Route::BlogPost{post_id:1},h4{"第一篇:Dioxus快速入门"}}p{"Dioxus组件、信号、事件基础教程。"}}div{style:"margin:12px 0;padding:8px;border:1px solid #eee;border-radius:4px;",Link{to:Route::BlogPost{post_id:2},h4{"第二篇:use_memo性能优化"}}p{"派生状态缓存,减少重复计算。"}}div{style:"margin:12px 0;padding:8px;border:1px solid #eee;border-radius:4px;",Link{to:Route::BlogPost{post_id:3},h4{"第三篇:路由系统详解"}}p{"嵌套布局、动态路由参数、404处理。"}}}}}// ========== 博客详情 BlogPost(动态路由参数post_id) ==========#[component]fnBlogPost(post_id:u32)->Element{letcontent=matchpost_id{1=>"Dioxus 是Rust生态下新一代声明式UI框架,支持Web、桌面端多端编译。",2=>"use_memo用来缓存派生计算,当依赖信号不变,不会重新执行计算逻辑。",3=>"Dioxus Router支持嵌套布局、动态路径参数、通配404路由。",_=>"文章不存在",};rsx!{div{h3{"文章 {post_id}"}p{"{content}"}}}}// ========== 用户主页 UserProfile(动态username参数) ==========#[component]fnUserProfile(username:String)->Element{rsx!{Layout{h1{"用户 {username} 的主页"}p{"这里展示该用户的个人信息与文章。"}}}}// ========== 404页面 PageNotFound(通配路由) ==========#[component]fnPageNotFound(segments:Vec<String>)->Element{rsx!{Layout{h1{"404 页面未找到"}p{"访问路径:{segments:?}"}Link{to:Route::Home{},"返回首页"}}}}核心知识点讲解
1. 路由开启:
Cargo.toml 需要开启 router 这个 feature,否则 Routable derive 宏不可用。
2. #[derive(Routable)]:
Dioxus路由核心宏,自动生成路由匹配、跳转相关代码。
3. 嵌套布局 #[layout(xxx)]
- BlogList、BlogPost 被 BlogLayout 包裹;
- Outlet:: {} :子路由页面渲染出口。
4. 动态路由参数:
/blog/:post_id 、 /user/:username ,路由参数直接作为组件函数入参;路径字符串会自动做类型转换(字符串→u32)。
5. 通配路由 /:…segments :
捕获所有未匹配路径,用于404页面。
6. Link:
路由导航标签,SPA单页跳转,不会刷新浏览器。
7. Layout(外层全局布局):
首页、关于、用户主页、404都放在Layout里面,统一导航栏、页脚。
易错点提醒:
BlogLayout只包裹博客相关页面,所以BlogList、BlogPost内部不要再套Layout;而Home/About/UserProfile/PageNotFound必须手动调用Layout组件。