<A/> 组件

普通的 HTML <a> 元素可以很好地支持客户端导航。路由器会为每个 <a> 元素添加一个监听器,并尝试在客户端处理点击事件,即无需再次向服务器请求 HTML,从而实现你在大多数现代 Web 应用中熟悉的快速“单页应用”(SPA)导航体验。

在以下情况中,路由器将不会处理 <a> 的点击事件:

  • 点击事件调用了 prevent_default()
  • 点击时按住了 MetaAltCtrlShift 键。
  • <a> 元素带有 targetdownload 属性,或者 rel="external"
  • 链接与当前页面不属于同一个源(origin)。

换句话说,只有当路由器确信可以处理时,才会尝试进行客户端导航,并会为每个 <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 中查看基于 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>
            // 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)
}