事件
介紹
Yew 與 web-sys
crate 集成,並使用該 crate 中的事件。下面的表格列出了在 html!
巨集中接受的所有 web-sys
事件。
您仍然可以為下表中未列出的事件新增 Callback
,請參閱手動事件監聽器。
事件類型
所有的事件類型都在 yew::events
下重新匯出。
使用 yew::events
中的類型比手動將 web-sys
作為依賴項包含在您的 crate 中更容易確保版本相容性,
因為您不會使用與 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
crate 有一個有用的trait:[JsCast
](https://rustwasm.github .io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html),它允許我們在類型之間直接轉換,只要它實現了JsCast
就行。我們可以謹慎地轉換,這涉及運行時檢查和處理 Option
和 Result
的邏輯,或者我們也可以冒險直接強行轉換。
多說無益,看代碼:
[dependencies]
# 需要 wasm-bindgen 用於呼叫 JsCast
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
是一個通用類型,將原來的物件回傳給你,以便再次嘗試轉換為別的類型。
這會兒你可能會想,什麼時候可以使用危險版本?在上面的情況下,它是安全的1,因為我們將 Callback
設定在一個沒有子元素的元素上,所以目標只能是同一個元素。
_1 只要牽涉到 JS 領域,就是安全的。 _
使用 TargetCast
**強烈建議先閱讀 使用 JsCast! **
TargetCast
的設計目的是讓新用戶了解 JsCast
的行為,但範圍更小,僅涉及事件及其目標。
選用 TargetCast
或 JsCast
純粹是個人偏好,實際您會發現 TargetCast
的實作和 JsCast
的功能很相似。
TargetCast
trait 是在 JsCast
基礎之上建構的,專門用於從事件中取得類型化的事件目標。
TargetCast
是 Yew 的一部分,因此無需添加依賴項即可在事件上使用 trait 方法,但它的工作方式與 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
,或者了解了這個 trait,您可能會發現 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
,我們不必將 String
傳回狀態,因為我們總是存取 input_node_ref
- 因此我們可以這樣做:
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| {
// 對 custard 做點什麼..
});
// 從 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| {
// 對 custard 做點什麼..
});
// 從 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 |