参数和查询
静态路径对于区分不同页面很有用,但几乎每个应用程序都希望在某个时候通过URL传递数据。
您可以通过两种方式做到这一点:
- 命名路由参数,如
/users/:id中的id - 命名路由查询,如
/search?q=Foo中的q
由于URL的构建方式,您可以从_任何_<Route/>视图访问查询。您可以从定义它们的<Route/>或其任何嵌套子项访问路由参数。
使用几个hook访问参数和查询非常简单:
每个都有一个类型化选项(use_query和use_params)和一个非类型化选项(use_query_map和use_params_map)。
非类型化版本保存一个简单的键值映射。要使用类型化版本,在结构体上派生Params trait。
Params是一个非常轻量级的trait,通过对每个字段应用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。使用stable,您只能在参数中使用
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的行为很像任何其他映射类型,有一个返回Option<String>的.get()方法。
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<_>的signal可能涉及几个步骤。但这样做是值得的,原因有两个:
- 它是正确的,即,它强制您考虑这些情况,"如果用户没有为这个查询字段传递值怎么办?如果他们传递无效值怎么办?"
- 它是高性能的。具体来说,当您在匹配相同
<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>
// 这个<nav>将在每个路由上显示,
// 因为它在<Routes/>之外
// 注意:我们可以只使用普通的<a>标签
// 路由器将使用客户端导航
<nav>
<a href="/">"Home"</a>
<a href="/contacts">"Contacts"</a>
</nav>
<main>
<Routes fallback=|| "Not found.">
// / 只有一个非嵌套的"Home"
<Route path=path!("/") view=|| view! {
<h3>"Home"</h3>
}/>
// /contacts 有嵌套路由
<ParentRoute
path=path!("/contacts")
view=ContactList
>
// 如果没有指定id,回退
<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>
// 如果没有指定id,回退
<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">
// 这里是我们的联系人列表组件本身
<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/>将显示嵌套的子路由
// 我们可以在布局中的任何地方定位这个outlet
<Outlet/>
</div>
}
}
#[component]
fn ContactInfo() -> impl IntoView {
// 我们可以使用`use_params_map`响应式地访问:id参数
let params = use_params_map();
let id = move || params.read().get("id").unwrap_or_default();
// 想象我们在这里从API加载数据
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/>是嵌套在
// /contacts/:id路由下的选项卡
<Outlet/>
</div>
}
}
fn main() {
leptos::mount::mount_to_body(App)
}