Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

参数与查询参数

Params and Queries

静态路径对于区分不同页面非常有用,但几乎每个应用程序在某些时候都需要通过 URL 传递数据。

Static paths are useful for distinguishing between different pages, but almost every application wants to pass data through the URL at some point.

你可以通过两种方式实现:

There are two ways you can do this:

  1. 命名路由 参数 (params),如 /users/:id 中的 id

  2. named route params like id in /users/:id

  3. 命名路由 查询参数 (queries),如 /search?q=Foo 中的 q

  4. named route queries like q in /search?q=Foo

由于 URL 的构建方式,你可以从 任何 <Route/> 视图访问查询参数。而路由参数则可以从定义它们的 <Route/> 及其任何嵌套子路由中访问。

Because of the way URLs are built, you can access the query from any <Route/> view. You can access route params from the <Route/> that defines them or any of its nested children.

使用几个钩子(hooks)访问参数和查询参数非常简单:

Accessing params and queries is pretty simple with a couple of hooks:

它们每一个都包含一个强类型选项(use_queryuse_params)和一个无类型选项(use_query_mapuse_params_map)。

Each of these comes with a typed option (use_query and use_params) and an untyped option (use_query_map and use_params_map).

无类型版本持有一个简单的键值对映射。要使用强类型版本,请在结构体上派生 Params trait。

The untyped versions hold a simple key-value map. To use the typed versions, derive the Params trait on a struct.

Params 是一个非常轻量级的 trait,通过对每个字段应用 FromStr,将字符串的扁平键值对映射转换为结构体。由于路由参数和 URL 查询参数的扁平结构,它的灵活性远不如像 serde 之类的库;但它为你的二进制文件增加的负担也小得多。

Params is a very lightweight trait to convert a flat key-value map of strings into a struct by applying FromStr to each field. Because of the flat structure of route params and URL queries, it’s significantly less flexible than something like serde; it also adds much less weight to your binary.

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 特性,则可以使用 TOption<T>

Note: The Params derive macro is located at leptos_router::params::Params.

Using stable, you can only use Option<T> in params. If you are using the nightly feature, you can use either T or Option<T>.

现在我们可以在组件中使用它们了。想象一个既有参数又有查询参数的 URL,例如 /contacts/:id?q=Search

Now we can use them in a component. Imagine a URL that has both params and a query, like /contacts/:id?q=Search.

强类型版本返回 Memo<Result<T, _>>。它是一个 Memo,因此它会响应 URL 的变化。它是一个 Result,因为参数或查询参数需要从 URL 解析,结果可能有效也可能无效。

The typed versions return Memo<Result<T, _>>. It’s a Memo so it reacts to changes in the URL. It’s a Result because the params or query need to be parsed from the URL, and may or may not be valid.

use leptos_router::hooks::{use_params, use_query};

let params = use_params::<ContactParams>();
let query = use_query::<ContactSearch>();

// id: || -> usize
// 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>

The untyped versions return Memo<ParamsMap>. Again, it’s a Memo to react to changes in the URL. ParamsMap behaves a lot like any other map type, with a .get() method that returns 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>
// id: || -> Option<String>
let id = move || params.read().get("id");

这可能会变得有点麻烦:导出一个包装了 Option<_>Result<_> 的信号可能需要几个步骤。但这样做是值得的,原因有二:

This can get a little messy: deriving a signal that wraps an Option<_> or Result<_> can involve a couple steps. But it’s worth doing this for two reasons:

  1. 它是正确的,也就是说,它迫使你考虑这些情况:“如果用户没有为这个查询字段传递值怎么办?如果他们传递了一个无效的值怎么办?”

  2. It’s correct, i.e., it forces you to consider the cases, “What if the user doesn’t pass a value for this query field? What if they pass an invalid value?”

  3. 它是高性能的。具体来说,当你在匹配同一个 <Route/> 的不同路径之间导航,且只有参数或查询参数发生变化时,你可以对应用程序的不同部分进行细粒度更新,而无需重新渲染。例如,在我们的联系人列表示例中,在不同联系人之间导航会对姓名栏(以及最终的联系人信息)进行定向更新,而无需替换或重新渲染包装它的 <Contact/>。这就是细粒度响应式的意义所在。

  4. It’s performant. Specifically, when you navigate between different paths that match the same <Route/> with only params or the query changing, you can get fine-grained updates to different parts of your app without rerendering. For example, navigating between different contacts in our contact-list example does a targeted update to the name field (and eventually contact info) without needing to replace or rerender the wrapping <Contact/>. This is what fine-grained reactivity is for.

这是上一节中的同一个示例。路由管理器是一个集成度很高的系统,因此提供一个突出多个特性的单一示例是有意义的,即使我们还没有解释完所有特性。

This is the same example from the previous section. The router is such an integrated system that it makes sense to provide a single example highlighting multiple features, even if we haven’t explained them all yet.

:::admonish sandbox title="在线示例" collapsible=true

点击打开 CodeSandbox。

Click to open CodeSandbox.

:::

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/> 之外
            // this <nav> will show on every routes,
            // because it's outside the <Routes/>
            // 注意:我们可以直接使用普通的 <a> 标签
            // 路由管理器将使用客户端导航
            // 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.">
                    // / 只有一个未嵌套的 "Home"
                    // / just has an un-nested "Home"
                    <Route path=path!("/") view=|| view! {
                        <h3>"Home"</h3>
                    }/>
                    // /contacts 有嵌套路由
                    // /contacts has nested routes
                    <ParentRoute
                        path=path!("/contacts")
                        view=ContactList
                      >
                        // 如果没有指定 id,回退
                        // 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>
                        // 如果没有指定 id,回退
                        // 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/> 将显示嵌套的子路由
            // 我们可以将此 outlet 放置在布局中的任何位置
            // <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 {
    // 我们可以使用 `use_params_map` 响应式地访问 :id 参数
    // 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();

    // 想象我们在这里从 API 加载数据
    // 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/> 是嵌套在
            // /contacts/:id 路由下的标签页
            // <Outlet/> here is the tabs that are nested
            // underneath the /contacts/:id route
            <Outlet/>
        </div>
    }
}

fn main() {
    leptos::mount::mount_to_body(App)
}