<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::use_navigate();
navigate("/somewhere", Default::default());

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

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

再一次,这与之前的例子相同。查看相关的 <A/> 组件,并查看 index.html 中的 CSS,以了解基于 ARIA 的样式。

实时示例

点击打开 CodeSandbox.

CodeSandbox 源码
use leptos::*;
use leptos_router::*;

#[component]
fn App() -> impl IntoView {
    view! {
        <Router>
            <h1>"Contact App"</h1>
            // 这个 <nav> 将显示在每个路由上,
            // 因为它在 <Routes/> 之外
            // 注意:我们可以只使用普通的 <a> 标签
            // 路由器将使用客户端导航
            <nav>
                <h2>"Navigation"</h2>
                <a href="/">"Home"</a>
                <a href="/contacts">"Contacts"</a>
            </nav>
            <main>
                <Routes>
                    // / 只有一个未嵌套的 "Home"
                    <Route path="/" view=|| view! {
                        <h3>"Home"</h3>
                    }/>
                    // /contacts 有嵌套路由
                    <Route
                        path="/contacts"
                        view=ContactList
                      >
                        // 如果没有指定 id,则回退
                        <Route path=":id" view=ContactInfo>
                            <Route path="" view=|| view! {
                                <div class="tab">
                                    "(Contact Info)"
                                </div>
                            }/>
                            <Route path="conversations" view=|| view! {
                                <div class="tab">
                                    "(Conversations)"
                                </div>
                            }/>
                        </Route>
                        // 如果没有指定 id,则回退
                        <Route path="" view=|| view! {
                            <div class="select-user">
                                "Select a user to view contact info."
                            </div>
                        }/>
                    </Route>
                </Routes>
            </main>
        </Router>
    }
}

#[component]
fn ContactList() -> impl IntoView {
    view! {
        <div class="contact-list">
            // 这是我们的联系人列表组件本身
            <div class="contact-list-contacts">
                <h3>"Contacts"</h3>
                <A href="alice">"Alice"</A>
                <A href="bob">"Bob"</A>
                <A href="steve">"Steve"</A>
            </div>

            // <Outlet/> 将显示嵌套的子路由
            // 我们可以将此出口放置在布局中的任何位置
            <Outlet/>
        </div>
    }
}

#[component]
fn ContactInfo() -> impl IntoView {
    // 我们可以使用 `use_params_map` 以响应式方式访问 :id 参数
    let params = use_params_map();
    let id = move || params.with(|params| params.get("id").cloned().unwrap_or_default());

    // 假设我们在这里从 API 加载数据
    let name = move || match id().as_str() {
        "alice" => "Alice",
        "bob" => "Bob",
        "steve" => "Steve",
        _ => "User not found.",
    };

    view! {
        <div class="contact-info">
            <h4>{name}</h4>
            <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_to_body(App)
}