ルーター (Router)
シングルページアプリケーション (SPA) のルーターは、URL に基づいて異なるページを表示する処理を行います。リンクをクリックしたときに異なるリモートリソースを要求するデフォルトの動作とは異なり、ルーターはアプリケーション内の有効なルートを指すようにローカルで URL を設定します。その後、ルーターはこの変更を検出し、レンダリングする内容を決定します。
Yew は yew-router
クレートでルーターサポートを提供します。使用を開始するには、依存関係を 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 />
は、ある Router コンポーネント(例: <BrowserRouter />
)の(深い)子要素である必要があります。通常、アプリケーションには 1 つの Router しか必要なく、通常は最上位の <App />
コンポーネントによって直ちにレンダリングされます。Router はコンテキストを登録し、これは Links と Switches の機能に必要です。以下に例を示します。
ブラウザ環境で 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 {id: String}
の代わりに通常の Post
バリアントを使用することもできます。例えば、Post
が別のルーターと一緒にレンダリングされる場合、そのフィールドは冗長になる可能性があります。詳細については、以下のネストされたルーターセクションを参照してください。
フィールドは Route
列挙型の一部として Clone + PartialEq
を実装する必要があることに注意してください。また、シリアル化と逆シリアル化のために 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 関数は News と id をレンダリングします。ここでは省略されています。
セグメントが 255 を超えると、u8::from_str()
は失敗し、ParseIntError
を返します。この場合、ルーターはルートが一致しないと見なします。
ルーティング構文やパラメータのバインディング方法の詳細については、route-recognizer を参照してください。
位置 (Location)
ルーターはコンテキストを介して一般的な Location
構造体を提供し、ルート情報にアクセスするために使用できます。これらはフックまたは ctx.link()
上の便利な関数を介して取得できます。
ナビゲーション
yew_router
はナビゲーションを処理するためのいくつかのツールを提供します。
リンク
<Link />
は <a>
要素としてレンダリングされ、onclick
イベントハンドラは preventDefault を呼び出し、ターゲットページを履歴にプッシュして必要なページをレンダリングします。これはシングルページアプリケーションに期待される動作です。通常のアンカー要素のデフォルトの onclick
はページをリロードします。
<Link />
コンポーネントはその子要素を <a>
要素に渡します。これはアプリ内ルーティングのための <a/>
の代替として考えることができます。違いは、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!" }</Link<Route>>
ナビゲーションインターフェース
ナビゲーター 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回クリックすると、同じ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}
</>
}
}
構造体コンポーネント
構造体コンポーネントの場合、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
は prelude に <Redirect />
コンポーネントも提供しています。これはナビゲーター API と同様の効果を実現するために使用できます。このコンポーネントは、ターゲットルートとして to
属性を受け取ります。<Redirect/>
がレンダリングされると、ユーザーは指定されたルートにリダイレクトされます。以下はその例です:
#[function_component(SomePage)]
fn some_page() -> Html {
// `use_user` フックを使用してユーザーを取得
let user = match use_user() {
Some(user) => user,
// ユーザーが `None` の場合、ログインページにリダイレクト
None => return html! {
<Redirect<Route> to={Route::Login}/>
},
};
// ... 実際のページ内容
}
Redirect
と Navigator
の選択方法Navigator API はコールバック内でルートを操作する唯一の方法です。
一方、<Redirect />
はコンポーネント内の戻り値として使用できます。また、ネストされたルーターの switch 関数など、他の非コンポーネントコンテキストでも <Redirect />
を使用することができます。
変更のリスニング
関数コンポーネント
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(
// イベントを処理する
))
.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>
}
}
ベースパス (Basename)
yew-router
を使用してベースパス (Basename) を定義できます。
ベースパスはすべてのルートの共通プレフィックスです。ナビゲーター API と <Switch />
コンポーネントはどちらもベースパスの設定をサポートしています。プッシュされるすべてのルートにはベースパスのプレフィックスが追加され、すべてのスイッチはパスを Routable
に解析する前にベースパスを削除します。
Router コンポーネントにベースパス属性が提供されていない場合、HTML ファイルの <base />
要素の href 属性を使用し、HTML ファイルに <base />
要素がない場合は /
にフォールバックします。