React: 事件处理
最后更新:2026-10-26
事件处理是 React 应用的 神经末梢——用户点击按钮、输入文字、提交表单,这些交互动作通过事件来响应。React 的事件系统比原生 DOM 事件更智能、更统一。
1. 你将学到
- React 合成事件与原生事件的区别
- 常用事件:onClick / onChange / onSubmit / onKeyDown
- 事件对象的处理
- 向事件处理函数传递参数
- 阻止默认行为和事件冒泡
2. 一个表单交互的故事
(1) 痛点:表单提交的 3 个坑
Bob 在做一个用户注册表单,用原生 JavaScript 写事件处理:
JAVASCRIPT
// 原生 JS:需要手动处理各种兼容性和细节
// 1. 获取 DOM 元素
const form = document.getElementById('register-form')
const nameInput = document.getElementById('name')
const submitBtn = document.getElementById('submit-btn')
// 2. 绑定事件
form.addEventListener('submit', function(event) {
event.preventDefault() // 阻止页面刷新
// 3. 手动获取表单数据
const formData = new FormData(form)
const data = Object.fromEntries(formData)
// 4. 提交数据
fetch('/api/register', {
method: 'POST',
body: JSON.stringify(data)
})
})
// 5. 输入实时校验
nameInput.addEventListener('input', function(event) {
if (event.target.value.length < 2) {
showError('姓名至少 2 个字符')
} else {
hideError()
}
})
// 6. 点击提交按钮时防止重复提交
let isSubmitting = false
submitBtn.addEventListener('click', function() {
if (isSubmitting) return
isSubmitting = true
submitBtn.disabled = true
submitBtn.textContent = '提交中...'
})
Bob 遇到的问题:
- 代码分散:事件绑定、数据处理、UI 更新分散在不同地方
- 内存泄漏:忘记移除事件监听器
- 兼容性问题:不同浏览器事件 API 有差异
(2) React 的事件处理
▶ 试一试收益:代码从 30 行缩到 15 行,没有 DOM 操作,没有事件解绑问题,没有浏览器兼容问题。
3. React 合成事件
React 的事件不是原生 DOM 事件,而是合成事件(SyntheticEvent)——React 在顶层统一监听,模拟了一套跨浏览器的事件系统。
| 特性 | 原生 DOM 事件 | React 合成事件 |
|---|---|---|
| 绑定方式 | addEventListener / onclick |
直接在 JSX 上写 |
| 浏览器兼容 | 需手动处理差异 | 自动统一 |
| 内存管理 | 需手动移除监听器 | 自动清理 |
| 事件对象 | 原生 Event | 合成 SyntheticEvent |
| 阻止冒泡 | event.stopPropagation() |
相同(标准化) |
| 阻止默认 | event.preventDefault() |
相同(标准化) |
graph TB
A[用户点击按钮] --> B[React 根节点捕获事件]
B --> C[创建合成事件对象]
C --> D[模拟冒泡/捕获阶段]
D --> E[调用 JSX 中绑定的 handler]
style B fill:#61dafb,color:#000
style C fill:#1890ff,color:#fff
输出:
TEXT
...
点击了 "Log In" 按钮
按钮被鼠标悬停
鼠标离开按钮
4. 常用事件速查
| 事件名 | 触发时机 | 常用场景 | 事件对象类型 |
|---|---|---|---|
onClick |
元素被点击 | 按钮、链接、卡片 | MouseEvent |
onChange |
输入框内容变化 | 表单输入、选择框 | ChangeEvent |
onSubmit |
表单提交 | 登录/注册/搜索 | FormEvent |
onFocus |
元素获得焦点 | 输入框高亮 | FocusEvent |
onBlur |
元素失去焦点 | 输入校验 | FocusEvent |
onKeyDown |
按键按下 | 快捷键、回车搜索 | KeyboardEvent |
onKeyUp |
按键抬起 | 实时搜索 | KeyboardEvent |
onMouseEnter |
鼠标进入 | 悬停效果 | MouseEvent |
onMouseLeave |
鼠标离开 | 悬停效果 | MouseEvent |
onScroll |
滚动 | 无限滚动加载 | UIEvent |
▶ 示例:常用事件综合展示
▶ 展开运行示例(81 行)
输出:
TEXT
带邮箱/密码的登录表单。邮箱无效 → 红色边框 + "The email address format is incorrect."。密码少于 6 位 → "Password must be at least 6 chars"。提交 → console.log({email, password})
5. 事件参数传递
(1) 直接传参
用箭头函数包裹,直接传入额外参数:
JSX
function UserList({ users, onDelete }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name}
{/* 箭头函数:传入 user.id 作为额外参数 */}
<button onClick={() => onDelete(user.id)}>
删除
</button>
</li>
))}
</ul>
)
}
// 使用
<UserList
users={users}
onDelete={(id) => console.log('删除用户:', id)}
/>
(2) 同时获取事件对象和自定义参数
JSX
function ColorPicker({ colors, onSelect }) {
return (
<div>
<p>选择颜色:</p>
{colors.map(color => (
<button
key={color}
onClick={(event) => {
// event:原生合成事件对象
// color:自定义参数
onSelect(color)
console.log('点击位置:', event.clientX, event.clientY)
}}
style={{
backgroundColor: color,
width: '40px',
height: '40px',
border: '2px solid #ddd',
borderRadius: '50%',
margin: '4px',
cursor: 'pointer'
}}
/>
))}
</div>
)
}
(3) 传递事件对象的 3 种方式
| 方式 | 写法 | 适用场景 |
|---|---|---|
| 隐式传递 | onClick={handleClick} |
不需要额外参数 |
| 箭头函数 | onClick={() => handleClick(id)} |
需要传递参数 |
| 同时传递 | onClick={(e) => handleClick(e, id)} |
需要事件对象 + 自定义参数 |
6. 阻止默认行为和冒泡
(1) 阻止默认行为:preventDefault()
最常用的场景是阻止表单提交刷新页面:
▶ 试一试(2) 阻止冒泡:stopPropagation()
▶ 试一试
7. 完整示例:Todo 列表(综合事件处理)
▶ 展开运行示例(111 行)
输出:
TEXT
... ('Log In:', { email, password })
点击了 "Log In" 按钮
按钮被鼠标悬停
鼠标离开按钮
交互流程:
- 输入框键入任务名,按回车或点"添加"按钮 → 任务加入列表
- 勾选复选框 → 任务标记完成(划掉文字 + 变灰)
- 点击 ✕ 按钮 → 删除该任务(
stopPropagation防止触发双击编辑)- 双击任意任务 → 弹出编辑对话框
▶ 示例 2:键盘快捷键与焦点管理
▶ 展开运行示例(95 行)
输出:
TEXT
自动聚焦输入框。Ctrl+S → "💾 Saved (Ctrl+S)",Ctrl+Z → "↩️ Undo (Ctrl+Z)",Escape → 清空,Enter → "✅ Confirm: [text]"。显示已按下的按键。
输出:
TEXT
副标题:"⌨️ Shortcut Keys Panel"。显示:"⌨️ Shortcut Keys Panel"。输入框:Type here,Try the shortcut keys...。useEffect 管理副作用。useRef 引用 DOM/变量
▶ 示例 3:拖拽排序事件处理
▶ 试一试输出:
TEXT
4 个可拖拽项(Apple、Banana、Cherry、Date)。拖拽以重新排序;被拖拽的项显示蓝色高亮边框。
输出:
TEXT
显示:"Drag to Reorder"。状态:items(setter: setItems)、dragIdx(setter: setDragIdx)
▶ 示例 4:自定义 Hook 封装事件监听
▶ 试一试输出:
TEXT
useEventListener hook:自动添加/移除 DOM 事件监听器。卸载时清理以防止内存泄漏
输出:
TEXT
状态:pos(setter: setPos)。useEffect 管理副作用。useCallback 记忆化处理函数
▶ 示例 5:表单提交与事件组合
JSX
function SearchForm({ onSearch }) {
const [query, setQuery] = useState('')
const [category, setCategory] = useState('all')
function handleSubmit(e) {
e.preventDefault()
onSearch({ query, category })
}
function handleReset() {
setQuery('')
setCategory('all')
}
return (
<form onSubmit={handleSubmit} style={{ maxWidth: 400, margin: '0 auto' }}>
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<input value={query} onChange={e => setQuery(e.target.value)}
placeholder="Search..." style={{ flex: 1, padding: 8, borderRadius: 4 }} />
<select value={category} onChange={e => setCategory(e.target.value)}
style={{ padding: 8, borderRadius: 4 }}>
<option value="all">All</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<button type="submit" style={{ padding: '6px 16px', cursor: 'pointer' }}>Search</button>
<button type="button" onClick={handleReset} style={{ padding: '6px 16px', cursor: 'pointer' }}>Reset</button>
</div>
</form>
)
}
输出:
TEXT
搜索栏 + 分类下拉框(All/Electronics/Books)。搜索提交 {query, category}。重置清空两个字段。
❓ 常见问题
Q 为什么用箭头函数
() => handleClick(id) 而不是直接 handleClick(id)?A 如果直接写
onClick={handleClick(id)},React 会在渲染时立即执行 handleClick(id),而不是等到点击时才执行。因为这是函数调用(有括号),不是函数引用(没有括号)。正确做法是 onClick={() => handleClick(id)}(箭头函数延迟执行)或 onClick={handleClick}(不传参时直接引用函数名)。Q 可以在一个元素上绑定多个相同事件吗?
A 不能直接绑两个
onClick。解决方案:① 在同一个处理函数里调用多个函数 onClick={() => { fn1(); fn2() }};② 或者把元素用多个 HOC 包装。通常一个元素只需要一个事件处理函数,在里面调用多个逻辑即可。Q React 合成事件和原生事件有什么区别?
A React 的 SyntheticEvent 是对原生事件的跨浏览器封装,提供统一的 API(如 e.preventDefault()、e.stopPropagation()),在所有浏览器中行为一致。合成事件在事件委托机制下工作——所有事件都挂载到 root 节点上,而不是各个 DOM 元素。React 17+ 事件委托到 root 而非 document,避免了多 React 版本共存时的冲突。
📖 小节
- React 使用合成事件(SyntheticEvent),跨浏览器兼容,自动清理
- 常用事件:onClick、onChange、onSubmit、onKeyDown、onFocus/onBlur
- 传递额外参数用箭头函数
onClick={() => handler(id)} event.preventDefault()阻止默认行为,event.stopPropagation()阻止冒泡- 不要写
onClick={handler(id)}(会立即执行),要用onClick={() => handler(id)}
📝 作业
- 基础题(难度⭐):创建一个
ButtonCounter组件,每点击一次按钮,显示的计数值 +1。使用onClick事件。 - 进阶题(难度⭐⭐):创建一个
SearchInput组件,实现"防抖搜索":用户停止输入 500ms 后自动触发搜索。使用onChange和useEffect。 - 挑战题(难度⭐⭐⭐):创建一个
DragAndDropList组件,实现拖拽排序功能。使用onDragStart、onDragOver、onDrop事件。列表项可拖动到新位置。