事件
介绍
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 领域,就是安全的。