跳至主要内容
版本:0.21

事件

简介

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。我们可以谨慎地做到这一点,其中涉及一些运行时检查和失败类型,如 OptionResult,或者我们可以危险地做到这一点。

少说多做,更多代码

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当涉及到 JS 领域时,任何事情都是安全的。

使用 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_intoJsCast::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-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>
}
}

有关 Closures 的更多信息,请参阅 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 事件类型
onabortEvent
onauxclickMouseEvent
onblurFocusEvent
oncancelEvent
oncanplayEvent
oncanplaythroughEvent
onchangeEvent
onclickMouseEvent
oncloseEvent
oncontextmenuMouseEvent
oncuechangeEvent
ondblclickMouseEvent
ondragDragEvent
ondragendDragEvent
ondragenterDragEvent
ondragexitDragEvent
ondragleaveDragEvent
ondragoverDragEvent
ondragstartDragEvent
ondropDragEvent
ondurationchangeEvent
onemptiedEvent
onendedEvent
onerrorEvent
onfocusFocusEvent
onfocusinFocusEvent
onfocusoutFocusEvent
onformdataEvent
oninputInputEvent
oninvalidEvent
onkeydownKeyboardEvent
onkeypressKeyboardEvent
onkeyupKeyboardEvent
onloadEvent
onloadeddataEvent
onloadedmetadataEvent
onloadstartProgressEvent
onmousedownMouseEvent
onmouseenterMouseEvent
onmouseleaveMouseEvent
onmousemoveMouseEvent
onmouseoutMouseEvent
onmouseoverMouseEvent
onmouseupMouseEvent
onpauseEvent
onplayEvent
onplayingEvent
onprogressProgressEvent
onratechangeEvent
onresetEvent
onresizeEvent
onscrollEvent
onsecuritypolicyviolationEvent
onseekedEvent
onseekingEvent
onselectEvent
onslotchangeEvent
onstalledEvent
onsubmitSubmitEvent
onsuspendEvent
ontimeupdateEvent
ontoggleEvent
onvolumechangeEvent
onwaitingEvent
onwheelWheelEvent
oncopyEvent
oncutEvent
onpasteEvent
onanimationcancelAnimationEvent
onanimationendAnimationEvent
onanimationiterationAnimationEvent
onanimationstartAnimationEvent
ongotpointercapturePointerEvent
onloadendProgressEvent
onlostpointercapturePointerEvent
onpointercancelPointerEvent
onpointerdownPointerEvent
onpointerenterPointerEvent
onpointerleavePointerEvent
onpointerlockchangeEvent
onpointerlockerrorEvent
onpointermovePointerEvent
onpointeroutPointerEvent
onpointeroverPointerEvent
onpointerupPointerEvent
onselectionchangeEvent
onselectstartEvent
onshowEvent
ontouchcancelTouchEvent
ontouchendTouchEvent
ontouchmoveTouchEvent
ontouchstartTouchEvent
ontransitioncancelTransitionEvent
ontransitionendTransitionEvent
ontransitionrunTransitionEvent
ontransitionstartTransitionEvent