ルーター
シングルページアプリケーション(SPA)におけるルーターは、URLに応じて異なるページを表示する役割を担います。リンクをクリックした際にデフォルトでリモートリソースを要求する代わりに、ルーターはローカルでURLを設定し、アプリケーション内の有効なルートを指し示します。その後、ルーターはこの変更を検出し、何をレンダリングするかを決定します。
Yewは、`yew-router` クレートでルーターサポートを提供しています。使用を開始するには、`Cargo.toml` に依存関係を追加します。
yew-router = { git = "https://github.com/yewstack/yew.git" }
必要なユーティリティは、`yew_router::prelude` で提供されています。
使用方法
`Route` の定義から始めます。
ルートは、`Routable` を導出する `enum` として定義されます。このenumは、`Clone + PartialEq` である必要があります。
use yew_router::prelude::*;
#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/")]
Home,
#[at("/secure")]
Secure,
#[not_found]
#[at("/404")]
NotFound,
}
`Route` は `
`yew-router` のコンポーネントのほとんど、特に `` と `
ブラウザ環境で `yew-router` を使用する場合、`
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>
}
}
パスセグメント
動的な名前付きワイルドカードセグメントを使用して、ルートから情報を抽出することも可能です。その後、`
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 {id: String}` の代わりに、通常の `Post` バリアントを使用することもできます。たとえば、`Post` が別のルーターでレンダリングされるとき、別のルーターがパスに一致して処理できるため、フィールドは冗長になる可能性があります。詳細については、以下の「ネストされたルーター」セクションを参照してください。
`Route` enumの一部として、フィールドは `Clone + PartialEq` を実装する必要があります。シリアル化と逆シリアル化のために、`std::fmt::Display` と `std::str::FromStr` も実装する必要があります。整数、浮動小数点数、Stringなどのプリミティブ型は、既にこれらの要件を満たしています。
パス形式が一致するが、逆シリアル化(`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` は、ナビゲーションを操作するための多くのツールを提供します。
リンク
`` は `` 要素としてレンダリングされ、`onclick` イベントハンドラーは preventDefault を呼び出し、ターゲットページを履歴にプッシュして目的のページをレンダリングします。これは、シングルページアプリで期待される動作です。通常のアンカー要素のデフォルトの `onclick` はページをリロードします。
`` コンポーネントは、その子要素を `` 要素にも渡します。アプリケーション内ルートの `` の代替とみなしてください。ただし、`href` の代わりに `to` 属性を指定します。使用例を次に示します。
<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` を使用しています。ターゲットルートがコンポーネントが存在するルートと同じである場合、または安全のために、通常のコールバックを使用してください。たとえば、すべてのページにあるロゴボタンを考えてみましょう。このボタンをクリックすると、ホームページに戻ります。ホームページでこのボタンを2回クリックすると、2回目のクリックで同じHomeルートがプッシュされ、`use_navigator`フックが再レンダリングをトリガーしないため、コードはパニックになります。
スタックに新しい場所をプッシュする代わりに、現在の場所を置き換えたい場合は、`navigator.push()` の代わりに `navigator.replace()` を使用します。
`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}
</>
}
}
構造体コンポーネント
構造体コンポーネントの場合、`Navigator` インスタンスは `ctx.link().navigator()` API を介して取得できます。残りは関数コンポーネントの場合と同じです。単一のボタンをレンダリングするビュー関数の例を次に示します。
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` は、prelude に `
#[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.
}
Navigator APIは、コールバックでルートを操作する唯一の方法です。一方、`
変更のリスニング
関数コンポーネント
`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::
クエリパラメーター
ナビゲーション時のクエリパラメーターの指定
新しいルートに移動する際にクエリパラメーターを指定するには、`navigator.push_with_query` 関数または `navigator.replace_with_query` 関数のいずれかを使用します。これは `serde` を使用してパラメーターをURLのクエリ文字列にシリアル化するため、`Serialize` を実装する任意の型を渡すことができます。最も単純な形式では、これは文字列のペアを含む `HashMap` です。
現在のルートのクエリパラメータを取得する
クエリパラメータを取得するには、location.query
を使用します。これは、URLのクエリ文字列からパラメータを逆シリアル化するためにserde
を使用します。
ネストされたルーター
アプリが大きくなった場合、ネストされたルーターが役立ちます。以下のルーター構造を考えてみてください。
ネストされた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コンポーネントにbasenameプロパティが提供されない場合、HTMLファイル内の<base />
要素のhref属性が使用され、HTMLファイルに<base />
が存在しない場合は/
がフォールバックされます。