跳至主要内容
版本:0.21

路由器

单页应用程序 (SPA) 中的路由器处理根据 URL 显示不同页面。当单击链接时,路由器不会像默认行为那样请求不同的远程资源,而是将 URL 本地设置为指向应用程序中的有效路由。然后,路由器检测到此更改,然后决定要呈现什么。

Yew 在 yew-router crate 中提供路由器支持。要开始使用它,请将依赖项添加到 Cargo.toml

yew-router = { git = "https://github.com/yewstack/yew.git" }

所需实用程序在 yew_router::prelude 下提供,

用法

首先定义一个 Route

路由被定义为派生自 Routableenum。此枚举必须为 Clone + PartialEq

use yew_router::prelude::*;

#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/")]
Home,
#[at("/secure")]
Secure,
#[not_found]
#[at("/404")]
NotFound,
}

Route<Switch /> 组件配对,该组件查找路径与浏览器的当前 URL 匹配的变体,并将其传递给 render 回调。然后,回调决定要呈现什么。如果未匹配任何路径,则路由器将导航到具有 not_found 属性的路径。如果未指定路由,则不会呈现任何内容,并且会向控制台记录一条消息,指出未匹配任何路由。

大多数 yew-router 的组件,特别是 <Link /><Switch />,必须是其中一个路由器组件(例如 <BrowserRouter />)的(孙)子组件。您通常只需要在应用程序中使用一个路由器,最常见的是由最顶层的 <App /> 组件立即呈现。路由器会注册一个上下文,这是链接和开关正常工作所必需的。下面显示了一个示例。

注意

在浏览器环境中使用 yew-router 时,强烈建议使用 <BrowserRouter />。您可以在 API 参考 中找到其他路由器风格。

use yew_router::prelude::*;
use yew::prelude::*;

#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/")]
Home,
#[at("/secure")]
Secure,
#[not_found]
#[at("/404")]
NotFound,
}

#[function_component(Secure)]
fn secure() -> Html {
let navigator = use_navigator().unwrap();

let onclick = Callback::from(move |_| navigator.push(&Route::Home));
html! {
<div>
<h1>{ "Secure" }</h1>
<button {onclick}>{ "Go Home" }</button>
</div>
}
}

fn switch(routes: Route) -> Html {
match routes {
Route::Home => html! { <h1>{ "Home" }</h1> },
Route::Secure => html! {
<Secure />
},
Route::NotFound => html! { <h1>{ "404" }</h1> },
}
}

#[function_component(Main)]
fn app() -> Html {
html! {
<BrowserRouter>
<Switch<Route> render={switch} /> // <- must be child of <BrowserRouter>
</BrowserRouter>
}
}

路径段

还可以使用动态和命名的通配符段从路由中提取信息。然后,您可以在 <Switch /> 中访问帖子的 ID,并通过属性将其转发到适当的组件。

use yew::prelude::*;
use yew_router::prelude::*;

#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/")]
Home,
#[at("/post/:id")]
Post { id: String },
#[at("/*path")]
Misc { path: String },
}

fn switch(route: Route) -> Html {
match route {
Route::Home => html! { <h1>{ "Home" }</h1> },
Route::Post { id } => html! {<p>{format!("You are looking at Post {}", id)}</p>},
Route::Misc { path } => html! {<p>{format!("Matched some other path: {}", path)}</p>},
}
}
注意

您还可以使用普通 Post 变体,而不是 Post {id: String}。例如,当 Post 使用其他路由器渲染时,该字段可以是多余的,因为其他路由器可以匹配和处理路径。有关详细信息,请参阅下面的 嵌套路由器 部分

请注意,字段必须实现 Clone + PartialEq 作为 Route 枚举的一部分。它们还必须实现 std::fmt::Displaystd::str::FromStr 以进行序列化和反序列化。整数、浮点数和字符串等基本类型已经满足要求。

当路径形式匹配,但反序列化失败(根据 FromStr)时。路由器将认为该路由不匹配,并尝试渲染未找到的路由(如果未指定未找到的路由,则为空白页)。

考虑以下示例

#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/news/:id")]
News { id: u8 },
#[not_found]
#[at("/404")]
NotFound,
}
// switch function renders News and id as is. Omitted here.

当片段超过 255 时,u8::from_str() 会因 ParseIntError 而失败,路由器将认为该路由不匹配。

router deserialization failure behavior

有关路由语法和如何绑定参数的更多信息,请查看 route-recognizer

位置

路由器通过上下文提供通用的 Location 结构,可用于访问路由信息。可以通过挂钩或 ctx.link() 上的便捷函数检索它们。

yew_router 提供了一些工具来处理导航。

<Link /> 呈现为 <a> 元素,onclick 事件处理程序将调用 preventDefault,并将目标页面推送到历史记录并呈现所需的页面,这是单页应用程序应有的预期。普通锚元素的默认 onclick 将重新加载页面。

<Link /> 组件还将其子项传递给 <a> 元素。可以将其视为应用程序内路由中 <a/> 的替换项。不同之处在于,您提供的是 to 属性,而不是 href。示例用法

<Link<Route> to={Route::Home}>{ "click here to go home" }</Link<Route>>

结构变体也能按预期工作

<Link<Route> to={Route::Post { id: "new-yew-release".to_string() }}>{ "Yew v0.19 out now!" }</Link<Route>>

Navigator API 同时为函数组件和结构组件提供。它们启用回调来更改路由。在任一情况下都可以获取 Navigator 实例来操作路由。

函数组件

对于函数组件,use_navigator 钩子在底层导航器提供程序更改时重新呈现组件。以下是如何实现一个在单击时导航到 Home 路由的按钮。

#[function_component(MyComponent)]
pub fn my_component() -> Html {
let navigator = use_navigator().unwrap();
let onclick = Callback::from(move |_| navigator.push(&Route::Home));

html! {
<>
<button {onclick}>{"Click to go home"}</button>
</>
}
}
注意

此处的示例使用 Callback::from。如果目标路由可以与组件所在的路由相同,或者只是为了安全起见,请使用普通回调。例如,考虑每个页面上的一个徽标按钮,单击该按钮时返回主页。在主页上单击该按钮两次会导致代码恐慌,因为第二次单击会推送一个相同的 Home 路由,并且 use_navigator 钩子不会触发重新呈现。

如果您想替换当前位置而不是将新位置推送到堆栈,请使用 navigator.replace() 而不是 navigator.push()

您可能会注意到 navigator 必须移入回调,因此不能再次用于其他回调。幸运的是,navigator 实现了 Clone,例如,以下是如何为不同的路由提供多个按钮

use yew::prelude::*;
use yew_router::prelude::*;

#[function_component(NavItems)]
pub fn nav_items() -> Html {
let navigator = use_navigator().unwrap();

let go_home_button = {
let navigator = navigator.clone();
let onclick = Callback::from(move |_| navigator.push(&Route::Home));
html! {
<button {onclick}>{"click to go home"}</button>
}
};

let go_to_first_post_button = {
let navigator = navigator.clone();
let onclick = Callback::from(move |_| navigator.push(&Route::Post { id: "first-post".to_string() }));
html! {
<button {onclick}>{"click to go the first post"}</button>
}
};

let go_to_secure_button = {
let onclick = Callback::from(move |_| navigator.push(&Route::Secure));
html! {
<button {onclick}>{"click to go to secure"}</button>
}
};

html! {
<>
{go_home_button}
{go_to_first_post_button}
{go_to_secure_button}
</>
}
}
结构组件

对于结构组件,可以通过 ctx.link().navigator() API 获取 Navigator 实例。其余部分与函数组件情况相同。以下是一个呈现单个按钮的视图函数示例。

fn view(&self, ctx: &Context<Self>) -> Html {
let navigator = ctx.link().navigator().unwrap();
let onclick = Callback::from(move |_| navigator.push(&MainRoute::Home));
html!{
<button {onclick}>{"Go Home"}</button>
}
}

重定向

yew-router 还提供了前奏中的 <Redirect /> 组件。它可用于实现与 navigator API 相似效果。该组件接受一个 to 属性作为目标路由。当呈现 <Redirect/> 时,用户将被重定向到 props 中指定的路由。以下是一个示例

#[function_component(SomePage)]
fn some_page() -> Html {
// made-up hook `use_user`
let user = match use_user() {
Some(user) => user,
// Redirects to the login page when user is `None`.
None => return html! {
<Redirect<Route> to={Route::Login}/>
},
};
// ... actual page content.
}
RedirectNavigator,使用哪一个

Navigator API 是在回调中操作路由的唯一方法。而 <Redirect /> 可用作组件中的返回值。你可能还希望在另一个非组件上下文中使用 <Redirect />,例如在 嵌套路由器 的 switch 函数中。

监听更改

函数组件

你可以使用 use_locationuse_route 钩子。当提供的值发生变化时,你的组件将重新呈现。

结构组件

为了对路由更改做出反应,你可以将回调闭包传递给 ctx.link()add_location_listener() 方法。

注意

一旦注销位置监听器,它将被注销。确保将句柄存储在组件状态中。

fn create(ctx: &Context<Self>) -> Self {
let listener = ctx.link()
.add_location_listener(ctx.link().callback(
// handle event
))
.unwrap();
MyComponent {
_listener: listener
}
}

ctx.link().location()ctx.link().route::<R>() 也可用于一次检索位置和路由。

查询参数

在导航时指定查询参数

要指定导航到新路由时的查询参数,请使用 navigator.push_with_querynavigator.replace_with_query 函数。它使用 serde 将参数序列化为 URL 的查询字符串,因此可以传递任何实现 Serialize 的类型。最简单的形式,这只是一个包含字符串对的 HashMap

获取当前路由的查询参数

location.query 用于获取查询参数。它使用 serde 从 URL 中的查询字符串反序列化参数。

嵌套路由器

当应用程序变得更大时,嵌套路由器可能很有用。考虑以下路由器结构

nested router structurenested router structure

嵌套的 SettingsRouter 处理以 /settings 开头的所有 URL。此外,它将不匹配的 URL 重定向到主 NotFound 路由。因此,/settings/gibberish 将重定向到 /404

注意

但请注意,这仍处于进行中,因此我们的做法并非最终做法

可以使用以下代码实现它

use yew::prelude::*;
use yew_router::prelude::*;
use gloo::utils::window;
use wasm_bindgen::UnwrapThrowExt;

#[derive(Clone, Routable, PartialEq)]
enum MainRoute {
#[at("/")]
Home,
#[at("/news")]
News,
#[at("/contact")]
Contact,
#[at("/settings")]
SettingsRoot,
#[at("/settings/*")]
Settings,
#[not_found]
#[at("/404")]
NotFound,
}

#[derive(Clone, Routable, PartialEq)]
enum SettingsRoute {
#[at("/settings")]
Profile,
#[at("/settings/friends")]
Friends,
#[at("/settings/theme")]
Theme,
#[not_found]
#[at("/settings/404")]
NotFound,
}

fn switch_main(route: MainRoute) -> Html {
match route {
MainRoute::Home => html! {<h1>{"Home"}</h1>},
MainRoute::News => html! {<h1>{"News"}</h1>},
MainRoute::Contact => html! {<h1>{"Contact"}</h1>},
MainRoute::SettingsRoot | MainRoute::Settings => html! { <Switch<SettingsRoute> render={switch_settings} /> },
MainRoute::NotFound => html! {<h1>{"Not Found"}</h1>},
}
}

fn switch_settings(route: SettingsRoute) -> Html {
match route {
SettingsRoute::Profile => html! {<h1>{"Profile"}</h1>},
SettingsRoute::Friends => html! {<h1>{"Friends"}</h1>},
SettingsRoute::Theme => html! {<h1>{"Theme"}</h1>},
SettingsRoute::NotFound => html! {<Redirect<MainRoute> to={MainRoute::NotFound}/>}
}
}

#[function_component(App)]
pub fn app() -> Html {
html! {
<BrowserRouter>
<Switch<MainRoute> render={switch_main} />
</BrowserRouter>
}
}

基名

可以使用 yew-router 定义基名。基名是所有路由的公共前缀。Navigator API 和 <Switch /> 组件都尊重基名设置。所有推送的路由都将以基名为前缀,所有开关都将在尝试将路径解析为 Routable 之前删除基名。

如果未向 Router 组件提供基名属性,它将使用 HTML 文件中 <base /> 元素的 href 属性,如果 HTML 文件中不存在 <base />,则回退到 /

相关示例

API 参考