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

<A/>组件

客户端导航与普通HTML <a>元素配合得非常好。路由器添加了一个监听器,处理对<a>元素的每次点击,并尝试在客户端处理它,即,不需要再次往返服务器请求HTML。这就是您可能从大多数现代Web应用程序中熟悉的快速"单页应用程序"导航的原因。

路由器在以下情况下会放弃处理<a>点击

  • 点击事件已调用prevent_default()
  • 点击时按住了MetaAltCtrlShift
  • <a>targetdownload属性,或rel="external"
  • 链接与当前位置有不同的来源

换句话说,路由器只有在相当确定可以处理时才会尝试进行客户端导航,它会升级每个<a>元素以获得这种特殊行为。

这也意味着如果您需要选择退出客户端路由,您可以轻松做到。例如,如果您有一个指向同一域上另一个页面的链接,但它不是您的Leptos应用程序的一部分,您可以只使用<a rel="external">来告诉路由器这不是它可以处理的东西。

路由器还提供了一个<A>组件,它做了两件额外的事情:

  1. 正确解析相对嵌套路由。使用普通<a>标签的相对路由可能很棘手。例如,如果您有一个像/post/:id这样的路由,<A href="1">将生成正确的相对路由,但<a href="1"可能不会(取决于它在您的视图中出现的位置)。<A/>相对于它出现的嵌套路由的路径解析路由。
  2. 如果此链接是活动链接(即,它是指向您所在页面的链接),则将aria-current属性设置为page。这对可访问性和样式很有帮助。例如,如果您想在链接指向您当前所在页面时设置不同的颜色,您可以使用CSS选择器匹配此属性。

程序化导航

您在页面之间导航的最常用方法应该是使用<a><form>元素或增强的<A/><Form/>组件。使用链接和表单进行导航是可访问性和优雅降级的最佳解决方案。

不过,有时您会想要程序化导航,即调用可以导航到新页面的函数。在这种情况下,您应该使用use_navigate函数。

let navigate = leptos_router::hooks::use_navigate();
navigate("/somewhere", Default::default());

您几乎永远不应该做像<button on:click=move |_| navigate(/* ... */)>这样的事情。任何导航的on:click都应该是<a>,出于可访问性的原因。

这里的第二个参数是一组NavigateOptions,包括相对于当前路由解析导航的选项(如<A/>组件所做的),在导航堆栈中替换它,包含一些导航状态,以及在导航时维护当前滚动状态。

再次,这是相同的示例。查看相对<A/>组件,并查看index.html中的CSS以查看基于ARIA的样式。

Live example

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/>之外
            // 注意:我们可以只使用普通的<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)
}