本文へスキップ
バージョン: 0.21

ルーター

シングルページアプリケーション(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` は `` コンポーネントとペアになり、ブラウザの現在のURLと一致するバリアントを見つけ、それを `render` コールバックに渡します。コールバックは、何をレンダリングするかを決定します。パスが一致しない場合、ルーターは `not_found` 属性を持つパスに移動します。ルートが指定されていない場合、何もレンダリングされず、ルートが一致しなかったことを示すメッセージがコンソールに出力されます。

`yew-router` のコンポーネントのほとんど、特に `` と `` は、ルーターコンポーネント(例:``)の子(または孫)である必要があります。通常、アプリケーションには1つのルーターしか必要なく、ほとんどの場合、最上位の `` コンポーネントによって直接レンダリングされます。ルーターはコンテキストを登録し、LinkとSwitchの機能に必要なコンテキストを提供します。例を以下に示します。

注意

ブラウザ環境で `yew-router` を使用する場合、`` を強くお勧めします。他のルーターの種類については、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>
}
}

パスセグメント

動的な名前付きワイルドカードセグメントを使用して、ルートから情報を抽出することも可能です。その後、`` 内で投稿の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 {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` で失敗し、ルーターはルートが一致しないと見なします。

router deserialization failure behavior

ルート構文とパラメーターのバインド方法の詳細については、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` インスタンスを取得できます。

関数コンポーネント

関数コンポーネントの場合、`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 に `` コンポーネントも提供します。これは、Navigator API と同様の効果を実現するために使用できます。このコンポーネントは、ターゲットルートとして `to` 属性を受け入れます。`` がレンダリングされると、ユーザーはプロパティで指定されたルートにリダイレクトされます。例を次に示します。

#[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は、コールバックでルートを操作する唯一の方法です。一方、`` はコンポーネントの戻り値として使用できます。また、`` を別の非コンポーネントコンテキスト(たとえば、ネストされたルーター の 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::()` も、場所とルートを一度に取得するために使用できます。

クエリパラメーター

ナビゲーション時のクエリパラメーターの指定

新しいルートに移動する際にクエリパラメーターを指定するには、`navigator.push_with_query` 関数または `navigator.replace_with_query` 関数のいずれかを使用します。これは `serde` を使用してパラメーターをURLのクエリ文字列にシリアル化するため、`Serialize` を実装する任意の型を渡すことができます。最も単純な形式では、これは文字列のペアを含む `HashMap` です。

現在のルートのクエリパラメータを取得する

クエリパラメータを取得するには、location.query を使用します。これは、URLのクエリ文字列からパラメータを逆シリアル化するためにserdeを使用します。

ネストされたルーター

アプリが大きくなった場合、ネストされたルーターが役立ちます。以下のルーター構造を考えてみてください。

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コンポーネントにbasenameプロパティが提供されない場合、HTMLファイル内の<base />要素のhref属性が使用され、HTMLファイルに<base />が存在しない場合は/がフォールバックされます。

関連する例

APIリファレンス