イベント
紹介
Yew は web-sys
クレートと統合されており、このクレートのイベントを使用します。以下の表には、html!
マクロで受け入れられるすべての web-sys
イベントが一覧表示されています。
下記の表に記載されていないイベントについても、Callback
を追加してリッスンすることができます。詳細は手動イベントリスナーを参照してください。
イベントタイプ
すべてのイベントタイプは yew::events
に再エクスポートされています。
yew::events
のタイプを使用することで、web-sys
を手動でクレートに依存関係として追加するよりも、バージョン互換性を確保しやすくなります。
Yew が指定するバージョンと競合するバージョンを使用することがなくなります。
イベントリスナーの名前は、html
マクロでイベント Callback
を追加する際に期待される名前です:
use yew::prelude::*;
html! {
<button onclick={Callback::from(|_| ())}>
// ^^^^^^^ event listener name
{ "Click me!" }
</button>
};
イベント名はリスナー名から "on" プレフィックスを削除したもので、したがって onclick
イベントリスナーは click
イベントをリッスンします。ページの最後にある完全なイベントリストとそのタイプを参照してください。
イベントキャプチャ
Yew がディスパッチするイベントは仮想 DOM 階層に従い、リスナーに向かってバブルアップします。現在、リスナーのバブルフェーズのみがサポートされています。仮想 DOM 階層は通常(ただし常にではありません)実際の DOM 階層と同じです。ポータルやその他の高度な技術を扱う際には、この違いが重要です。よく設計されたコンポーネントでは、直感的にイベントは子コンポーネントから親コンポーネントにバブルアップするはずです。これにより、html!
で記述した階層がイベントハンドラによって観察される階層となります。
イベントのバブルアップを避けたい場合は、アプリケーションを起動する前に以下のコードを呼び出すことができます
yew::set_event_bubbling(false);
アプリケーションを起動する前に。これによりイベント処理が高速化されますが、期待されるイベントを受信しないために一部のコンポーネントが動作しなくなる可能性があります。慎重に使用してください!
イベントデリゲート
驚くかもしれませんが、イベントリスナーはレンダリングされた要素に直接登録されるわけではありません。代わりに、イベントは Yew アプリケーションのサブツリーのルートノードから委譲されます。ただし、イベントはそのネイティブ形式で渡され、合成形式は作成されません。これにより、HTML リスナーで予期されるイベントと Yew で発生するイベントとの間に不一致が生じる可能性があります。
Event::current_target
はリスナーが追加された要素ではなく、Yew サブツリーのルートノードを指します。基になるHtmlElement
にアクセスしたい場合は、NodeRef
を使用してください。Event::event_phase
は常にEvent::CAPTURING_PHASE
です。内部的には、イベントはバブリングフェーズにあるかのように振る舞い、イベント伝播が再生され、イベントは上位にバブルアップします。つまり、仮想 DOM 内の上位のイベントリスナーが下位のイベントリスナーの後にトリガーされます。現在、Yew はキャプチャリスナーをサポートしていません。
これも意味するところは、Yew によって登録されたイベントは通常、他のイベントリスナーよりも先にトリガーされるということです。
型付きイベントターゲット
このセクションでは、target (Event.target
) は常にイベントが発生した要素を指します。
これは必ずしも Callback
が配置された要素を指すわけではありません。
イベント Callback
の中で、イベントのターゲットを取得したい場合があります。例えば、change
イベントは何かが変更されたことを通知するだけで、具体的な情報を提供しません。
Yew では、正しい型でターゲット要素を取得する方法がいくつかあり、ここで順を追って説明します。イベント上の web_sys::Event::target
を呼び出すと、オプションの web_sys::EventTarget
型が返されますが、入力要素の値を知りたい場合にはあまり役に立たないかもしれません。
以下のすべての方法で、同じ問題を解決します。これにより、方法の違いが明確になり、問題に対処することができます。
問題:
<input>
要素に onchange
Callback
があり、呼び出されるたびにコンポーネントに更新 Msg
を送信したいとします。
Msg
列挙型は次のようになります:
pub enum Msg {
InputValue(String),
}
JsCast
の使用
wasm-bindgen
クレートには便利なトレイトがあります:JsCast
。これにより、型が JsCast
を実装している限り、型間の直接キャストが可能になります。慎重にキャストすることもできますが、これはランタイムチェックと Option
や Result
のロジックを処理することを伴います。また、強制的にキャストすることもできます。
コードを見てみましょう:
[dependencies]
# JsCast を呼び出すために wasm-bindgen が必要です
wasm-bindgen = "0.2"
use wasm_bindgen::JsCast;
use web_sys::{EventTarget, HtmlInputElement};
use yew::prelude::*;
#[function_component]
fn MyComponent() -> Html {
let input_value_handle = use_state(String::default);
let input_value = (*input_value_handle).clone();
let on_cautious_change = {
let input_value_handle = input_value_handle.clone();
Callback::from(move |e: Event| {
// イベントが作成されたとき、ターゲットは未定義であり、ディスパッチされるときにのみターゲットが追加されます。
let target: Option<EventTarget> = e.target();
// イベントはバブルアップする可能性があるため、
// このリスナーは HtmlInputElement 型ではない子要素のイベントをキャッチする可能性があります。
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
input_value_handle.set(input.value());
}
})
};
let on_dangerous_change = Callback::from(move |e: Event| {
let target: EventTarget = e
.target()
.expect("Event should have a target when dispatched");
// target が HtmlInputElement であることを理解している必要があります。
// そうでない場合、value を呼び出すと未定義の動作(UB)になります。
// ここでは、これが入力要素であることを確信しているため、チェックせずに適切な型に変換できます。
input_value_handle.set(target.unchecked_into::<HtmlInputElement>().value());
});
html! {
<>
<label for="cautious-input">
{ "My cautious input:" }
<input onchange={on_cautious_change}
id="cautious-input"
type="text"
value={input_value.clone()}
/>
</label>
<label for="dangerous-input">
{ "My dangerous input:" }
<input onchange={on_dangerous_change}
id="dangerous-input"
type="text"
value={input_value}
/>
</label>
</>
}
}
JsCast
が提供するメソッドは dyn_into
と unchecked_into
です。これらのメソッドを使用すると、EventTarget
から HtmlInputElement
への変換が可能になります。dyn_into
メソッドは慎重で、実行時に型が実際に HtmlInputElement
であるかどうかをチェックし、そうでない場合は Err(JsValue)
を返します。JsValue
は汎用型で、元のオブジェクトを返し、別の型への変換を再試行することができます。
ここで、危険なバージョンを使用するタイミングについて考えるかもしれません。上記のケースでは、子要素のない要素に Callback
を設定しているため、ターゲットは同じ要素である必要があるため、安全です1。
1 JS の領域に関わる限り、安全です。
TargetCast
の使用
JsCast の使用 を先に読むことを強くお勧めします!
TargetCast
は新しいユーザーが JsCast
の動作を理解するために設計されていますが、範囲はイベントとそのターゲットに限定されています。
TargetCast
または JsCast
を選択するのは純粋に個人の好みの問題であり、実際には TargetCast
の実装と JsCast
の機能は非常に似ています。
TargetCast
トレイトは JsCast
の上に構築されており、イベントから型付きのイベントターゲットを取得するために特化されています。
TargetCast
は Yew の一部であるため、依存関係を追加せずにイベント上でトレイトメソッドを使用できますが、その動作は JsCast
と非常に似ています。
use web_sys::HtmlInputElement;
use yew::prelude::*;
#[function_component]
fn MyComponent() -> Html {
let input_value_handle = use_state(String::default);
let input_value = (*input_value_handle).clone();
let on_cautious_change = {
let input_value_handle = input_value_handle.clone();
Callback::from(move |e: Event| {
let input = e.target_dyn_into::<HtmlInputElement>();
if let Some(input) = input {
input_value_handle.set(input.value());
}
})
};
let on_dangerous_change = Callback::from(move |e: Event| {
// target が HtmlInputElement であることを理解している必要があります。
// そうでない場合、value を呼び出すと未定義の動作(UB)になります。
input_value_handle.set(e.target_unchecked_into::<HtmlInputElement>().value());
});
html! {
<>
<label for="cautious-input">
{ "My cautious input:" }
<input onchange={on_cautious_change}
id="cautious-input"
type="text"
value={input_value.clone()}
/>
</label>
<label for="dangerous-input">
{ "My dangerous input:" }
<input onchange={on_dangerous_change}
id="dangerous-input"
type="text"
value={input_value}
/>
</label>
</>
}
}
もし JsCast
についてすでに知っているか、このトレイトに精通している場合、TargetCast::target_dyn_into
が JsCast::dyn_into
に似ていることに気付くでしょうが、イベントのターゲットに特化しています。TargetCast::target_unchecked_into
は JsCast::unchecked_into
に似ているため、上記の JsCast
に関するすべての警告が TargetCast
にも適用されます。
NodeRef
の使用
NodeRef
は、与えられたイベントを Callback
に渡す代わりに使用できます。
use web_sys::HtmlInputElement;
use yew::prelude::*;
#[function_component]
fn MyComponent() -> Html {
let input_node_ref = use_node_ref();
let input_value_handle = use_state(String::default);
let input_value = (*input_value_handle).clone();
let onchange = {
let input_node_ref = input_node_ref.clone();
Callback::from(move |_| {
let input = input_node_ref.cast::<HtmlInputElement>();
if let Some(input) = input {
input_value_handle.set(input.value());
}
})
};
html! {
<>
<label for="my-input">
{ "My input:" }
<input ref={input_node_ref}
{onchange}
id="my-input"
type="text"
value={input_value}
/>
</label>
</>
}
}
NodeRef
を使用すると、イベントを無視して NodeRef::cast
メソッドを使用して Option<HtmlInputElement>
を取得できます。これはオプションであり、NodeRef
を設定する前に cast
を呼び出すか、型が一致しない場合に None
を返します。
NodeRef
を使用することで、常に input_node_ref
にアクセスできるため、状態に String
を送信する必要がないことがわかるかもしれません。したがって、次のようにすることができます:
use web_sys::HtmlInputElement;
use yew::prelude::*;
#[function_component]
fn MyComponent() -> Html {
let input_node_ref = use_node_ref();
let onchange = {
let input_node_ref = input_node_ref.clone();
Callback::from(move |_| {
if let Some(input) = input_node_ref.cast::<HtmlInputElement>() {
let value = input.value();
// value に対して何かを行う
}
})
};
html! {
<>
<label for="my-input">
{ "My input:" }
<input ref={input_node_ref}
{onchange}
id="my-input"
type="text"
/>
</label>
</>
}
}
どの方法を選択するかは、コンポーネントと個人の好みによります。推奨される方法はありません。
手動イベントリスナー
Yew の html
マクロがサポートしていないイベントをリッスンしたい場合があります。サポートされているイベントのリストはこちらを参照してください。
手動で要素にイベントリスナーを追加するには、NodeRef
を使用して、use_effect_with
内で web-sys
と wasm-bindgen API を使用してリスナーを追加します。
以下の例では、架空の custard
イベントにリスナーを追加する方法を示します。Yew がサポートしていないすべてのイベントやカスタムイベントは、web_sys::Event
として表現できます。カスタム/サポートされていないイベントの特定のメソッドやフィールドにアクセスする必要がある場合は、JsCast
のメソッドを使用して必要な型に変換できます。
Closure
を使用する(冗長バージョン)
直接 web-sys
と wasm-bindgen
のインターフェースを使用するのは少し面倒かもしれません……なので、心の準備をしてください(gloo
のおかげで、より簡潔な方法があります)。
use wasm_bindgen::{prelude::Closure, JsCast};
use web_sys::HtmlElement;
use yew::prelude::*;
#[function_component]
fn MyComponent() -> Html {
let div_node_ref = use_node_ref();
use_effect_with(
div_node_ref.clone(),
{
let div_node_ref = div_node_ref.clone();
move |_| {
let mut custard_listener = None;
if let Some(element) = div_node_ref.cast::<HtmlElement>() {
// 通常作成する Callback を作成
let oncustard = Callback::from(move |_: Event| {
// カスタードに対して何かを行う..
});
// Box<dyn Fn> から Closure を作成 - これは 'static である必要があります
let listener =
Closure::<dyn Fn(Event)>::wrap(
Box::new(move |e: Event| oncustard.emit(e))
);
element
.add_event_listener_with_callback(
"custard",
listener.as_ref().unchecked_ref()
)
.unwrap();
custard_listener = Some(listener);
}
move || drop(custard_listener)
}
}
);
html! {
<div ref={div_node_ref} id="my-div"></div>
}
}
Closure
の詳細については、wasm-bindgen ガイド を参照してください。
gloo
を使用する(簡潔なバージョン)
より便利な方法は、gloo
、具体的には gloo_events
を使用することです。
これは web-sys
、wasm-bindgen
の高レベル抽象実装です。
gloo_events
は、イベントリスナーを作成および保存するために使用できる EventListener
型を提供します。
[dependencies]
gloo-events = "0.1"
use web_sys::HtmlElement;
use yew::prelude::*;
use gloo::events::EventListener;
#[function_component]
fn MyComponent() -> Html {
let div_node_ref = use_node_ref();
use_effect_with(
div_node_ref.clone(),
{
let div_node_ref = div_node_ref.clone();
move |_| {
let mut custard_listener = None;
if let Some(element) = div_node_ref.cast::<HtmlElement>() {
// 通常作成する Callback を作成
let oncustard = Callback::from(move |_: Event| {
// カスタードに対して何かを行う..
});
// Box<dyn Fn> から Closure を作成 - これは 'static である必要があります
let listener = EventListener::new(
&element,
"custard",
move |e| oncustard.emit(e.clone())
);
custard_listener = Some(listener);
}
move || drop(custard_listener)
}
}
);
html! {
<div ref={div_node_ref} id="my-div"></div>
}
}
EventListener
の詳細については、gloo_events docs.rs を参照してください。
利用可能なイベントの完全なリスト
リスナー名 | web_sys イベントの種類 |
---|---|
onabort | Event |
onauxclick | MouseEvent |
onblur | FocusEvent |
oncancel | Event |
oncanplay | Event |
oncanplaythrough | Event |
onchange | Event |
onclick | MouseEvent |
onclose | Event |
oncontextmenu | MouseEvent |
oncuechange | Event |
ondblclick | MouseEvent |
ondrag | DragEvent |
ondragend | DragEvent |
ondragenter | DragEvent |
ondragexit | DragEvent |
ondragleave | DragEvent |
ondragover | DragEvent |
ondragstart | DragEvent |
ondrop | DragEvent |
ondurationchange | Event |
onemptied | Event |
onended | Event |
onerror | Event |
onfocus | FocusEvent |
onfocusin | FocusEvent |
onfocusout | FocusEvent |
onformdata | Event |
oninput | InputEvent |
oninvalid | Event |
onkeydown | KeyboardEvent |
onkeypress | KeyboardEvent |
onkeyup | KeyboardEvent |
onload | Event |
onloadeddata | Event |
onloadedmetadata | Event |
onloadstart | ProgressEvent |
onmousedown | MouseEvent |
onmouseenter | MouseEvent |
onmouseleave | MouseEvent |
onmousemove | MouseEvent |
onmouseout | MouseEvent |
onmouseover | MouseEvent |
onmouseup | MouseEvent |
onpause | Event |
onplay | Event |
onplaying | Event |
onprogress | ProgressEvent |
onratechange | Event |
onreset | Event |
onresize | Event |
onscroll | Event |
onsecuritypolicyviolation | Event |
onseeked | Event |
onseeking | Event |
onselect | Event |
onslotchange | Event |
onstalled | Event |
onsubmit | SubmitEvent |
onsuspend | Event |
ontimeupdate | Event |
ontoggle | Event |
onvolumechange | Event |
onwaiting | Event |
onwheel | WheelEvent |
oncopy | Event |
oncut | Event |
onpaste | Event |
onanimationcancel | AnimationEvent |
onanimationend | AnimationEvent |
onanimationiteration | AnimationEvent |
onanimationstart | AnimationEvent |
ongotpointercapture | PointerEvent |
onloadend | ProgressEvent |
onlostpointercapture | PointerEvent |
onpointercancel | PointerEvent |
onpointerdown | PointerEvent |
onpointerenter | PointerEvent |
onpointerleave | PointerEvent |
onpointerlockchange | Event |
onpointerlockerror | Event |
onpointermove | PointerEvent |
onpointerout | PointerEvent |
onpointerover | PointerEvent |
onpointerup | PointerEvent |
onselectionchange | Event |
onselectstart | Event |
onshow | Event |
ontouchcancel | TouchEvent |
ontouchend | TouchEvent |
ontouchmove | TouchEvent |
ontouchstart | TouchEvent |
ontransitioncancel | TransitionEvent |
ontransitionend | TransitionEvent |
ontransitionrun | TransitionEvent |
ontransitionstart | TransitionEvent |