事件
简介
Yew 与 web-sys
箱集成,并使用该箱中的事件。 下表 列出了 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 注册的事件通常会在其他事件侦听器之前触发。
类型化事件目标
在本节中,目标 (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]
# 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_into
和 unchecked_into
,你可能已经看到了,它们允许我们从 EventTarget
转到 HtmlInputElement
。dyn_into
方法很谨慎,因为在运行时它会检查类型是否实际上是 HtmlInputElement
,如果不是,则返回 Err(JsValue)
,JsValue
是一个万能类型,本质上是给你返回对象以再次尝试。
在这一点上,你可能在想...在什么时候使用危险版本是合适的?在上面的情况下,它是安全的1,因为我们在没有子元素的元素上设置了 Callback
,所以目标只能是同一个元素。
1当涉及到 JS 领域时,任何事情都是安全的。
使用 TargetCast
强烈建议先阅读 使用 JsCast!
TargetCast
被设计为与 JsCast
非常相似 - 这是为了让新用户能够感受到 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| {
// 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
,我们不必将 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();
// 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-sys
和wasm-bindgen API 添加侦听器。
以下示例将演示如何为虚构的 custard
事件添加侦听器。所有不受 yew 支持或自定义的事件都可以表示为web_sys::Event
。如果您需要访问自定义/不受支持事件上的特定方法或字段,则可以使用JsCast
的方法来转换为所需的类型。
使用 Closure
(冗长)
直接使用 web-sys
和 wasm-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>
}
}
有关 Closures
的更多信息,请参阅 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>() {
// 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 | 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 |