路由器
单页应用程序 (SPA) 中的路由器处理根据 URL 显示不同页面。当单击链接时,路由器不会像默认行为那样请求不同的远程资源,而是将 URL 本地设置为指向应用程序中的有效路由。然后,路由器检测到此更改,然后决定要呈现什么。
Yew 在 yew-router
crate 中提供路由器支持。要开始使用它,请将依赖项添加到 Cargo.toml
yew-router = { git = "https://github.com/yewstack/yew.git" }
所需实用程序在 yew_router::prelude
下提供,
用法
首先定义一个 Route
。
路由被定义为派生自 Routable
的 enum
。此枚举必须为 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::Display
和 std::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
而失败,路由器将认为该路由不匹配。
有关路由语法和如何绑定参数的更多信息,请查看 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 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.
}
Redirect
与 Navigator
,使用哪一个Navigator API 是在回调中操作路由的唯一方法。而 <Redirect />
可用作组件中的返回值。你可能还希望在另一个非组件上下文中使用 <Redirect />
,例如在 嵌套路由器 的 switch 函数中。
监听更改
函数组件
你可以使用 use_location
和 use_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_query
或 navigator.replace_with_query
函数。它使用 serde
将参数序列化为 URL 的查询字符串,因此可以传递任何实现 Serialize
的类型。最简单的形式,这只是一个包含字符串对的 HashMap
。
获取当前路由的查询参数
location.query
用于获取查询参数。它使用 serde
从 URL 中的查询字符串反序列化参数。
嵌套路由器
当应用程序变得更大时,嵌套路由器可能很有用。考虑以下路由器结构
嵌套的 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 />
,则回退到 /
。