事件
介绍
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
,它允许我们在类型之间直接转换,只要它实现了 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 |