5.1 事件绑定
> **本节目标**:完整掌握 `on*` 事件属性的全部写法——命令绑定、方法名绑定、带参数绑定。
5.1.1 事件绑定的语法
RML 使用标准 HTML 的 `on*` 属性绑定事件:
<button on-click={submit}>提交</button>
<input oninput={handle_input} />
<div onmouseenter={show_tooltip}>悬停我</div>
5.1.2 三种绑定方式
方式一:命令绑定(推荐)
直接绑定 `#[command]` 标记的方法:
#[command]
pub fn submit(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
// 提交逻辑
cx.notify();
}
<button on-click={submit}>提交</button>
**优点**:
- 编译期检查命令是否存在 - 类型安全,参数类型在编译期验证 - 与 `#[command]` 宏紧密集成
方式二:方法名绑定
用字符串形式绑定方法名:
// 不需要 #[command] 标记
pub fn handle_click(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
// 处理逻辑
cx.notify();
}
<button on-click="handle_click">提交</button>
**特点**:
- 方法不需要 `#[command]` 标记 - 字符串形式,编译期不检查 - 适用于内部辅助方法
⚠️ **注意**:方法名绑定的方法签名必须与命令方法相同(接收事件对象和 cx)。
方式三:带参数绑定
传递额外参数给命令:
#[command]
pub fn delete_item(&mut self, id: u64, _: &ClickEvent, cx: &mut ViewContext<Self>) {
self.items.retain(|i| i.id != id);
cx.notify();
}
<button on-click={delete_item, {item.id}}>删除</button>
5.1.3 完整事件列表
鼠标事件
| 事件属性 | 触发时机 | 事件对象 | | ------------- | ---------- | --------------- | | `on-click` | 点击 | `ClickEvent` | | `ondblclick` | 双击 | `ClickEvent` | | `onmousedown` | 鼠标按下 | `MouseEvent` | | `onmouseup` | 鼠标释放 | `MouseEvent` | | `onmouseenter` | 鼠标进入元素 | `MouseEvent` | | `onmouseleave` | 鼠标离开元素 | `MouseEvent` | | `onmousemove` | 鼠标在元素上移动 | `MouseEvent` | | `onwheel` | 滚轮滚动 | `WheelEvent` |
键盘事件
| 事件属性 | 触发时机 | 事件对象 | | ----------- | ------ | --------------- | | `onkeydown` | 键盘按下 | `KeyDownEvent` | | `onkeyup` | 键盘释放 | `KeyUpEvent` |
表单事件
| 事件属性 | 触发时机 | 事件对象 | | ---------- | -------------- | -------------- | | `oninput` | 输入框值实时变化 | `InputEvent` | | `onchange` | 值变化(失去焦点后) | `ChangeEvent` | | `onsubmit` | 表单提交 | `SubmitEvent` | | `onfocus` | 获得焦点 | `FocusEvent` | | `onblur` | 失去焦点 | `FocusEvent` |
其他事件
| 事件属性 | 触发时机 | 事件对象 | | -------------- | ---------- | -------------- | | `onload` | 元素加载完成 | `LoadEvent` | | `onresize` | 窗口大小变化 | `ResizeEvent` | | `onscroll` | 滚动 | `ScrollEvent` |
5.1.4 事件绑定的示例
点击事件
<button on-click={increment}>+1</button>
<button on-click={decrement}>-1</button>
<button on-click={reset}>重置</button>
输入事件
<input
value={search_text}
oninput={on_search_input}
placeholder="搜索..."
/>
键盘事件
<input
value={new_todo}
onkeydown={on_enter_key}
placeholder="按回车添加"
/>
鼠标悬停
<div
onmouseenter={show_tooltip}
onmouseleave={hide_tooltip}
class="tooltip-target"
>
悬停查看详情
</div>
焦点事件
<input
onfocus={on_input_focus}
onblur={on_input_blur}
placeholder="点击聚焦"
/>
5.1.5 带参数的事件绑定
单参数
<button on-click={delete_item, {item.id}}>删除</button>
<button on-click={edit_item, {item.id}}>编辑</button>
<button on-click={toggle_status, {item.id}}>切换状态</button>
多参数
<button on-click={update_status, {item.id}, 'completed'}>标记完成</button>
<button on-click={update_status, {item.id}, 'pending'}>标记待办</button>
<button on-click={move_item, {item.id}, {target_index}}>移动</button>
参数类型
<!-- 数字 -->
<button on-click={set_priority, {item.id}, 1}>高优先级</button>
<!-- 字符串 -->
<button on-click={set_category, {item.id}, 'work'}>工作</button>
<!-- 布尔 -->
<button on-click={set_flag, {item.id}, true}>启用</button>
<!-- 字段引用 -->
<button on-click={copy_item, {source.id}, {target.id}}>复制</button>
5.1.6 事件绑定的常见模式
模式一:单一命令
最简单的模式,一个事件绑定一个命令:
<button on-click={submit}>提交</button>
模式二:条件命令
根据条件决定是否执行:
#[command]
pub fn submit(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
if !self.can_submit() {
return;
}
// 提交逻辑
}
<button on-click={submit} disabled={!can_submit}>提交</button>
模式三:链式命令
一个事件触发多个命令:
#[command]
pub fn on_enter_key(&mut self, ev: &KeyDownEvent, cx: &mut ViewContext<Self>) {
if ev.key == Key::Enter {
self.validate_input(cx);
self.submit(&ClickEvent::default(), cx);
self.clear_input(cx);
}
}
<input onkeydown={on_enter_key} value={input_text} />
模式四:事件转发
父视图处理子组件的事件:
<SearchBox on_search={handle_search} placeholder="搜索..." />
#[command]
pub fn handle_search(&mut self, query: SharedString, _: &SearchEvent, cx: &mut ViewContext<Self>) {
self.search_query = query;
self.perform_search(cx);
}
5.1.7 事件绑定的注意事项
命令必须存在
<!-- ❌ 命令不存在,编译错误 -->
<button on-click={non_existent}>点击</button>
参数类型必须匹配
#[command]
pub fn delete_item(&mut self, id: u64, _: &ClickEvent, cx: &mut ViewContext<Self>) {
// id 类型是 u64
}
<!-- ❌ 参数类型不匹配 -->
<button on-click={delete_item, 'string_id'}>删除</button>
<!-- ✅ 参数类型匹配 -->
<button on-click={delete_item, {item.id}}>删除</button>
事件对象类型必须匹配
// 命令声明的事件对象类型
#[command]
pub fn on_click(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) { ... }
<!-- ✅ on-click 对应 ClickEvent -->
<button on-click={on_click}>点击</button>
<!-- ❌ onkeydown 对应 KeyDownEvent,不匹配 -->
<button onkeydown={on_click}>点击</button>
5.1.8 事件绑定的最佳实践
1. 用动词命名命令
// ✅ 动词开头
#[command]
pub fn submit(&mut self, ...) { ... }
#[command]
pub fn delete_item(&mut self, ...) { ... }
// ❌ 名词或事件名
#[command]
pub fn button_click(&mut self, ...) { ... } // 应为 submit
#[command]
pub fn on_click(&mut self, ...) { ... } // 应为 submit
2. 命令与事件语义对应
<!-- ✅ 语义对应 -->
<button on-click={submit}>提交</button> <!-- 点击 = 提交 -->
<input onkeydown={on_enter_key} /> <!-- 键盘 = 回车处理 -->
<!-- ❌ 语义混乱 -->
<button on-click={on_input_change}>提交</button> <!-- 点击 ≠ 输入变化 -->
3. 复杂逻辑用辅助方法
#[command]
pub fn on_enter_key(&mut self, ev: &KeyDownEvent, cx: &mut ViewContext<Self>) {
if ev.key == Key::Enter {
self.submit_form(cx); // 调用辅助方法
}
}
// 辅助方法,不标注 #[command]
fn submit_form(&mut self, cx: &mut ViewContext<Self>) {
if self.validate() {
self.save_data(cx);
self.show_success(cx);
}
}
4. 避免在事件中做重计算
// ❌ 每次点击都重计算
#[command]
pub fn on_sort(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
self.items.sort_by(|a, b| {
// 复杂排序逻辑...
});
cx.notify();
}
// ✅ 用计算属性缓存
#[computed]
pub fn sorted_items(&self) -> Vec<&Item> {
let mut items: Vec<_> = self.items.iter().collect();
items.sort_by(|a, b| {
// 复杂排序逻辑...
});
items
}
5.1.9 小结
RML 的事件绑定提供三种方式:
- **命令绑定**:`on-click={command}`(推荐) - **方法名绑定**:`on-click="method_name"` - **带参数绑定**:`on-click={command, {param}}`
掌握完整的事件列表和绑定方式,你就能处理任何用户交互。
下一节 → [5.2 事件对象](./event-objects.md)