跳至主要內容
版本:0.21

事件

簡介

Yew 整合 web-sys 板條箱並使用該板條箱中的事件。 下表 列出 web-sys 中所有在 html! 巨集中可接受的事件。

您仍可以針對下表中未列出的事件加入 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 app 的子樹根節點委派事件。即便如此,事件還是會以原生形式傳遞,且不會建立合成表單。這可能導致在 HTML 監聽器預期的事件,以及 Yew 中顯示的事件之間不匹配。

  • Event::current_target 指向的是 Yew 子樹根節點,而不是監聽器所加入的元素。如果您想要存取底層的 HtmlElement,請使用 NodeRef

  • Event::event_phase 永遠都是 Event::CAPTURING_PHASE。在內部,事件會表現得好像在冒泡階段 (bubbling phase) 一樣,事件傳播會被重播,而事件會 「向上」冒泡,換句話說,在虛擬 DOM 中位階較高的事件監聽器會在它們下方的事件監聽器 之後 觸發。目前,Yew 不支援攔截監聽器。

    這也表示 Yew 註冊的事件通常會在其他事件監聽器之前觸發。

已輸入型別的事件目標

小心

在此區段,目標 (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,或者我們可以危險地進行這項動作。

講得夠多了,來看看實際程式碼

Cargo.toml
[dependencies]
# need wasm-bindgen for 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| {
// When events are created the target is undefined, it's only
// when dispatched does the target get added.
let target: Option<EventTarget> = e.target();
// Events can bubble so this listener might catch events from child
// elements which are not of type 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");
// You must KNOW target is a HtmlInputElement, otherwise
// the call to value would be Undefined Behaviour (UB).
// Here we are sure that this is input element so we can convert it to the appropriate type without checking
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_intounchecked_into,你可能會看到這些方法能讓我們從 EventTarget 轉換到 HtmlInputElementdyn_into 方法很謹慎,因為它會在執行時期檢查此種類是否真的是 HtmlInputElement,如果不是,則會傳回一個 Err(JsValue)JsValue 是一個通用的類型,基本上會提供你一個物件,讓你再試一次。

在這個階段,你可能會想... 什麼時候使用危險的版本才是正確的?以上面的例子來說,使用危險版本是安全的1,因為我們已經在一個沒有子代元素的元素上設定了 Callback,所以目標只能是同一個元素。

1 在 JavaScript 土地上,能安全的地方就叫做安全。

使用 TargetCast

強烈建議先閱讀 使用 JsCast

注意事項

TargetCast 的設計宗旨與 JsCast 非常類似,目的是讓新使用者能了解 JsCast 的行為,不過範圍縮小到事件及其目標。

TargetCastJsCast 純粹是喜好問題,你會發現 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| {
// You must KNOW target is a HtmlInputElement, otherwise
// the call to value would be Undefined Behaviour (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,所以我們可以執行下列動作

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();
// do something with 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-syswasm-bindgen API 加入一個監聽器。

以下範例會顯示如何新增用於自訂 custard 事件的監聽器。所有由 yew 不支援或自訂的事件都可以表示為 web_sys::Event。如果你需要存取自訂/不支援事件中的特定方法或欄位,則可以使用 JsCast 的方法才能轉換為所需的類型。

使用 Closure(冗長)

直接使用 web-syswasm-bindgen API 來執行這件事可能會有點痛苦... 因此請做好心理準備(感謝 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>() {
// Create your Callback as you normally would
let oncustard = Callback::from(move |_: Event| {
// do something about custard..
});

// Create a Closure from a Box<dyn Fn> - this has to be '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-syswasm-bindgen 的抽象化延伸。

gloo_events 具有 EventListener 類型,可使用它建立和儲存事件聆聽器。

Cargo.toml
[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>() {
// Create your Callback as you normally would
let oncustard = Callback::from(move |_: Event| {
// do something about custard..
});

// Create a Closure from a Box<dyn Fn> - this has to be '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事件
onauxclick滑鼠事件
onblur焦點事件
oncancel事件
oncanplay事件
oncanplaythrough事件
onchange事件
onclick滑鼠事件
onclose事件
oncontextmenu滑鼠事件
oncuechange事件
ondblclick滑鼠事件
ondrag拖曳事件
ondragend拖曳事件
ondragenter拖曳事件
ondragexit拖曳事件
ondragleave拖曳事件
ondragover拖曳事件
ondragstart拖曳事件
ondrop拖曳事件
ondurationchange事件
onemptied事件
onended事件
onerror事件
onfocus焦點事件
onfocusin焦點事件
onfocusout焦點事件
onformdata事件
oninput輸入事件
oninvalid事件
onkeydown鍵盤事件
onkeypress鍵盤事件
onkeyup鍵盤事件
onload事件
onloadeddata事件
onloadedmetadata事件
onloadstart進度事件
onmousedown滑鼠事件
onmouseenter滑鼠事件
onmouseleave滑鼠事件
onmousemove滑鼠事件
onmouseout滑鼠事件
onmouseover滑鼠事件
onmouseup滑鼠事件
onpause事件
onplay事件
onplaying事件
onprogress進度事件
onratechange事件
onreset事件
onresize事件
onscroll事件
onsecuritypolicyviolation事件
onseeked事件
onseeking事件
onselect事件
onslotchange事件
onstalled事件
onsubmit提交事件
onsuspend事件
ontimeupdate事件
ontoggle事件
onvolumechange事件
onwaiting事件
onwheel滾輪事件
oncopy事件
oncut事件
onpaste事件
onanimationcancel動畫事件
onanimationend動畫事件
onanimationiteration動畫事件
onanimationstart動畫事件
ongotpointercapture指標事件
onloadend進度事件
onlostpointercapture指標事件
onpointercancel指標事件
onpointerdown指標事件
onpointerenter指標事件
onpointerleave指標事件
onpointerlockchange事件
onpointerlockerror事件
onpointermove指標事件
onpointerout指標事件
onpointerover指標事件
onpointerup指標事件
onselectionchange事件
onselectstart事件
onshow事件
ontouchcancel觸控事件
ontouchend觸控事件
ontouchmove觸控事件
ontouchstart觸控事件
ontransitioncancel切換事件
ontransitionend切換事件
ontransitionrun切換事件
ontransitionstart切換事件