第六章:路由与导航
博客v1.0系列教程(Dioxus)博客 v1.0 系列教程 (Dioxus)
第六章:路由与导航
1. 为什么需要路由
单页应用(SPA)的所有代码在同一个页面加载,通过路由控制显示哪个"页面"组件:
/ → 首页
/blog → 文章列表
/article/1 → 文章详情
/login → 登录页
路由的核心能力:
- URL 与组件一一对应
- 浏览器前进/后退正常工作
- 支持链接和程序化导航
2. 定义路由
2.1 路由枚举
use dioxus::prelude::*;
#[derive(Routable, Clone, PartialEq)]
enum Route {
#[route("/")]
Home {},
#[route("/blog")]
BlogList {},
#[route("/article/:slug")]
BlogPost { slug: String },
#[route("/login")]
Login {},
}
2.2 路由组件与 App
// 每个页面组件
#[component]
fn Home() -> Element {
rsx! { h1 { "首页" } }
}
#[component]
fn BlogList() -> Element {
rsx! { h1 { "文章列表" } }
}
#[component]
fn BlogPost(slug: String) -> Element {
rsx! { h1 { "文章: {slug}" } }
}
#[component]
fn Login() -> Element {
rsx! { h1 { "登录" } }
}
// App 中挂载路由
#[component]
fn App() -> Element {
rsx! {
Router::<Route> {}
}
}
2.3 Cargo.toml
[dependencies]
dioxus = { version = "0.7", features = ["router"] }
3. 页面导航
3.1 Link 组件
use dioxus::prelude::*;
#[component]
fn NavBar() -> Element {
rsx! {
nav { class: "flex gap-4 p-4",
Link { to: Route::Home {}, "首页" }
Link { to: Route::BlogList {}, "文章" }
Link { to: Route::Login {}, "登录" }
}
}
}
3.2 use_navigator 编程式导航
#[component]
fn LoginPage() -> Element {
let navigator = use_navigator();
let do_login = move |_| {
// 登录成功后跳转
navigator.push(Route::Home {});
};
rsx! {
button { onclick: do_login, "登录" }
}
}
// 更多导航方式
navigator.push(Route::Home {}); // 推入新页面
navigator.replace(Route::Home {}); // 替换当前页面
navigator.go_back(); // 后退
4. 路由参数
4.1 动态路径参数
#[derive(Routable, Clone, PartialEq)]
enum Route {
#[route("/article/:slug")]
BlogPost { slug: String },
#[route("/category/:id")]
Category { id: u32 },
#[route("/user/:user_id/post/:post_id")] // 多参数
UserPost { user_id: String, post_id: String },
}
// 在组件中接收参数
#[component]
fn BlogPost(slug: String) -> Element {
// slug 自动从 URL 中提取
rsx! { h1 { "查看文章: {slug}" } }
}
4.2 查询参数
use serde::Deserialize;
#[derive(Deserialize)]
struct SearchParams {
q: String,
page: Option<u32>,
}
#[component]
fn SearchPage() -> Element {
// 从 URL 查询参数中读取 ?q=xxx&page=1
let params = use_route().query::<SearchParams>();
match params {
Ok(p) => rsx! {
div { "搜索: {p.q}, 页码: {p.page.unwrap_or(1)}" }
},
Err(_) => rsx! {
div { "请在 URL 中提供 ?q=xxx 参数" }
},
}
}
5. 嵌套路由与布局
5.1 布局组件
#[derive(Routable, Clone, PartialEq)]
enum Route {
#[layout(DefaultLayout)] // 此类路由共享一个布局
#[route("/")]
Home {},
#[route("/blog")]
BlogList {},
#[end_layout]
#[route("/login")]
Login {}, // 没有布局包裹
}
#[component]
fn DefaultLayout() -> Element {
rsx! {
div { class: "min-h-screen",
NavBar {} // 所有页面共享导航栏
main { class: "p-6",
Outlet::<Route> {} // 子页面内容在此渲染
}
Footer {}
}
}
}
5.2 嵌套路由
#[derive(Routable, Clone, PartialEq)]
enum Route {
#[nest("/admin")]
#[layout(AdminLayout)]
#[route("/admin")]
AdminDashboard {},
#[route("/admin/articles")]
ArticleManager {},
#[route("/admin/settings")]
Settings {},
#[end_nest]
}
6. 404 页面
#[derive(Routable, Clone, PartialEq)]
enum Route {
// ... 其他路由
#[route("/:404")]
NotFound {},
}
#[component]
fn NotFound() -> Element {
rsx! {
div { class: "text-center py-16",
h1 { class: "text-6xl font-bold mb-4", "404" }
p { class: "text-lg mb-6", "页面未找到" }
Link { to: Route::Home {}, "返回首页" }
}
}
}
7. 链接高亮
use dioxus::prelude::*;
#[component]
fn NavLink(to: Route, label: String) -> Element {
let current = use_route::<Route>();
let is_active = current == to;
rsx! {
Link {
class: "px-4 py-2 rounded-lg text-sm transition-colors {
if is_active { \"bg-blue-500 text-white\" } else { \"text-gray-600\" }
}",
to,
"{label}"
}
}
}
// 使用
NavLink { to: Route::Home {}, label: "首页" }
NavLink { to: Route::BlogList {}, label: "文章" }
8. 路由守卫
// 需要登录才能访问的路由
#[component]
fn ProtectedRoute(children: Element) -> Element {
let user = use_context::<Signal<Option<User>>>();
if user.read().is_none() {
return rsx! {
Redirect { to: Route::Login {} }
};
}
rsx! { {children} }
}
// 在路由中使用
#[derive(Routable, Clone, PartialEq)]
enum Route {
#[layout(ProtectedRoute)]
#[route("/admin")]
Admin {},
#[end_layout]
}
9. 小结
#[derive(Routable)]枚举定义所有路由Router::<Route> {}渲染根路由Link组件声明式导航,use_navigator编程式导航:param动态路径参数,?query=查询参数#[layout]和#[nest]实现布局共享和嵌套路由/:404捕获未匹配路由- 下一章将学习样式与 Tailwind CSS 集成
dioxusrouternavigationroutablelink