参数和查询
静态路径对于区分不同的页面很有用,但几乎每个应用程序都需要通过 URL 传递数据。
有两种方法可以实现:
- 使用命名路由参数,例如
/users/:id中的id - 使用命名路由查询,例如
/search?q=Foo中的q
由于 URL 的构造方式,你可以从任何 <Route/> 视图访问查询(query)。而对于路由参数(params),你可以从定义它们的 <Route/> 或它的任何嵌套子路由中访问。
通过几个钩子函数,可以很简单地访问参数和查询:
这些函数分别提供了类型化选项(use_query 和 use_params)和非类型化选项(use_query_map 和 use_params_map)。
非类型化版本会返回一个简单的键值映射。而对于类型化版本,可以在结构体上派生 Params 特性。
Params是一个非常轻量级的特性,用于通过对每个字段应用FromStr将字符串的扁平键值映射转换为结构体。由于路由参数和 URL 查询的扁平结构,它比类似serde的工具灵活性低得多,但也大大减少了对二进制文件的负担。
use leptos::Params;
use leptos_router::params::Params;
#[derive(Params, PartialEq)]
struct ContactParams {
id: Option<usize>,
}
#[derive(Params, PartialEq)]
struct ContactSearch {
q: Option<String>,
}
注意:
Params派生宏位于leptos_router::params::Params。如果使用稳定版,你只能在参数中使用
Option<T>。如果启用了nightly特性,你可以使用T或Option<T>。
现在可以在组件中使用它们。假设一个同时包含参数和查询的 URL,例如 /contacts/:id?q=Search。
类型化版本返回 Memo<Result<T, _>>。它是一个 Memo,因此会响应 URL 的变化。同时,它是一个 Result,因为参数或查询需要从 URL 中解析,解析结果可能有效也可能无效。
use leptos_router::hooks::{use_params, use_query};
let params = use_params::<ContactParams>();
let query = use_query::<ContactSearch>();
// id: || -> usize
let id = move || {
params
.read()
.as_ref()
.ok()
.and_then(|params| params.id)
.unwrap_or_default()
};
非类型化版本返回 Memo<ParamsMap>。同样,它是 Memo,用于响应 URL 的变化。ParamsMap 的行为类似于其他映射类型,提供 .get() 方法来返回 Option<String>。
use leptos_router::hooks::{use_params_map, use_query_map};
let params = use_params_map();
let query = use_query_map();
// id: || -> Option<String>
let id = move || params.read().get("id");
这样做可能会显得有些繁琐:派生一个信号来封装 Option<_> 或 Result<_> 可能需要一些步骤。但这样做是值得的,原因有两点:
- 正确性:它迫使你考虑以下情况,“如果用户没有为查询字段传递值怎么办?如果传递了无效值怎么办?”
- 性能:具体来说,当你在匹配相同
<Route/>的路径之间导航时,仅参数或查询发生变化时,可以对应用程序的不同部分进行细粒度更新,而无需重新渲染。例如,在联系人列表示例中,在不同联系人之间导航时,会针对性地更新名称字段(以及联系信息),而无需替换或重新渲染整个<Contact/>组件。这正是细粒度响应式的用途所在。
这实际上是上一节的示例。由于路由器系统是一个整体,所以提供了一个单一示例来展示多个特性,即使我们还没有全部解释清楚,也不必惊讶。
CodeSandbox Source
use leptos::prelude::*;
use leptos_router::components::{Outlet, ParentRoute, Route, Router, Routes, A};
use leptos_router::hooks::use_params_map;
use leptos_router::path;
#[component]
pub fn App() -> impl IntoView {
view! {
<Router>
<h1>"Contact App"</h1>
// this <nav> will show on every routes,
// because it's outside the <Routes/>
// note: we can just use normal <a> tags
// and the router will use client-side navigation
<nav>
<a href="/">"Home"</a>
<a href="/contacts">"Contacts"</a>
</nav>
<main>
<Routes fallback=|| "Not found.">
// / just has an un-nested "Home"
<Route path=path!("/") view=|| view! {
<h3>"Home"</h3>
}/>
// /contacts has nested routes
<ParentRoute
path=path!("/contacts")
view=ContactList
>
// if no id specified, fall back
<ParentRoute path=path!(":id") view=ContactInfo>
<Route path=path!("") view=|| view! {
<div class="tab">
"(Contact Info)"
</div>
}/>
<Route path=path!("conversations") view=|| view! {
<div class="tab">
"(Conversations)"
</div>
}/>
</ParentRoute>
// if no id specified, fall back
<Route path=path!("") view=|| view! {
<div class="select-user">
"Select a user to view contact info."
</div>
}/>
</ParentRoute>
</Routes>
</main>
</Router>
}
}
#[component]
fn ContactList() -> impl IntoView {
view! {
<div class="contact-list">
// here's our contact list component itself
<h3>"Contacts"</h3>
<div class="contact-list-contacts">
<A href="alice">"Alice"</A>
<A href="bob">"Bob"</A>
<A href="steve">"Steve"</A>
</div>
// <Outlet/> will show the nested child route
// we can position this outlet wherever we want
// within the layout
<Outlet/>
</div>
}
}
#[component]
fn ContactInfo() -> impl IntoView {
// we can access the :id param reactively with `use_params_map`
let params = use_params_map();
let id = move || params.read().get("id").unwrap_or_default();
// imagine we're loading data from an API here
let name = move || match id().as_str() {
"alice" => "Alice",
"bob" => "Bob",
"steve" => "Steve",
_ => "User not found.",
};
view! {
<h4>{name}</h4>
<div class="contact-info">
<div class="tabs">
<A href="" exact=true>"Contact Info"</A>
<A href="conversations">"Conversations"</A>
</div>
// <Outlet/> here is the tabs that are nested
// underneath the /contacts/:id route
<Outlet/>
</div>
}
}
fn main() {
leptos::mount::mount_to_body(App)
}