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/> 组件

The <A/> Component

使用普通的 HTML <a> 元素,客户端导航也能完美运行。路由管理器添加了一个监听器,用于处理对 <a> 元素的每一次点击,并尝试在客户端处理它,即无需再次往返服务器请求 HTML。这就是让你在大多数现代 Web 应用中倍感亲切的、迅速的“单页应用”导航。

Client-side navigation works perfectly fine with ordinary HTML <a> elements. The router adds a listener that handles every click on a <a> element and tries to handle it on the client side, i.e., without doing another round trip to the server to request HTML. This is what enables the snappy “single-page app” navigations you’re probably familiar with from most modern web apps.

在以下几种情况下,路由管理器会放弃处理 <a> 点击:

The router will bail out of handling an <a> click under a number of situations

  • 点击事件已调用 prevent_default()

  • the click event has had prevent_default() called on it

  • 点击时按住了 MetaAltCtrlShift

  • the Meta, Alt, Ctrl, or Shift keys were held during click

  • <a> 具有 targetdownload 属性,或者 rel="external"

  • the <a> has a target or download attribute, or rel="external"

  • 链接的源(origin)与当前位置不同

  • the link has a different origin from the current location

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

In other words, the router will only try to do a client-side navigation when it’s pretty sure it can handle it, and it will upgrade every <a> element to get this special behavior.

这也意味着如果你需要退出客户端路由,可以很容易地做到。例如,如果你有一个指向同一域名下但不属于 Leptos 应用的其他页面的链接,你只需使用 <a rel="external"> 来告诉路由管理器这不是它能处理的东西。

This also means that if you need to opt out of client-side routing, you can do so easily. For example, if you have a link to another page on the same domain, but which isn’t part of your Leptos app, you can just use <a rel="external"> to tell the router it isn’t something it can handle.

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

The router also provides an <A> component, which does two additional things:

  1. 正确解析相对嵌套路由。使用普通的 <a> 标签进行相对路由可能会很棘手。例如,如果你有一个像 /post/:id 这样的路由,<A href="1"> 会生成正确的相对路由,但 <a href="1"> 很可能不会(取决于它在视图中出现的位置)。<A/> 会相对于它所在的嵌套路由路径来解析路由。

  2. Correctly resolves relative nested routes. Relative routing with ordinary <a> tags can be tricky. For example, if you have a route like /post/:id, <A href="1"> will generate the correct relative route, but <a href="1"> likely will not (depending on where it appears in your view.) <A/> resolves routes relative to the path of the nested route within which it appears.

  3. 如果此链接是当前活动链接(即指向你当前所在页面的链接),则将 aria-current 属性设置为 page。这对于可访问性和样式设置非常有帮助。例如,如果你想在链接指向当前页面时将其设置为不同的颜色,你可以使用 CSS 选择器匹配此属性。

  4. Sets the aria-current attribute to page if this link is the active link (i.e., it’s a link to the page you’re on). This is helpful for accessibility and for styling. For example, if you want to set the link a different color if it’s a link to the page you’re currently on, you can match this attribute with a CSS selector.

以编程方式导航

在页面之间导航最常用的方法应该是使用 <a><form> 元素,或者使用增强的 <A/><Form/> 组件。使用链接和表单进行导航是实现可访问性和平稳退化的最佳方案。

Your most-used methods of navigating between pages should be with <a> and <form> elements or with the enhanced <A/> and <Form/> components. Using links and forms to navigate is the best solution for accessibility and graceful degradation.

不过,有时你可能想以编程方式导航,即调用一个可以导航到新页面的函数。在这种情况下,你应该使用 use_navigate 函数。

On occasion, though, you’ll want to navigate programmatically, i.e., call a function that can navigate to a new page. In that case, you should use the use_navigate function.

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

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

You should almost never do something like <button on:click=move |_| navigate(/* ... */)>. Any on:click that navigates should be an <a>, for reasons of accessibility.

这里的第二个参数是一组 NavigateOptions,其中包括像 <A/> 组件那样相对于当前路由解析导航、在导航栈中替换当前记录、包含一些导航状态以及在导航时保持当前滚动状态等选项。

The second argument here is a set of NavigateOptions, which includes options to resolve the navigation relative to the current route as the <A/> component does, replace it in the navigation stack, include some navigation state, and maintain the current scroll state on navigation.

再次强调,这是同一个示例。请查看相对路径的 <A/> 组件,并查看 index.html 中的 CSS 以了解基于 ARIA 的样式设置。

Once again, this is the same example. Check out the relative <A/> components, and take a look at the CSS in index.html to see the ARIA-based styling.

:::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 route,
            // 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)
}