React: 事件处理

最后更新:2026-10-26

事件处理是 React 应用的 神经末梢——用户点击按钮、输入文字、提交表单,这些交互动作通过事件来响应。React 的事件系统比原生 DOM 事件更智能、更统一。


1. 你将学到



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 遇到的问题:

(2) React 的事件处理

▶ 试一试

收益:代码从 30 行缩到 15 行,没有 DOM 操作,没有事件解绑问题,没有浏览器兼容问题。



3. React 合成事件

React 的事件不是原生 DOM 事件,而是合成事件(SyntheticEvent)——React 在顶层统一监听,模拟了一套跨浏览器的事件系统。

特性 原生 DOM 事件 React 合成事件
绑定方式 addEventListener / onclick 直接在 JSX 上写
浏览器兼容 需手动处理差异 自动统一
内存管理 需手动移除监听器 自动清理
事件对象 原生 Event 合成 SyntheticEvent
阻止冒泡 event.stopPropagation() 相同(标准化)
阻止默认 event.preventDefault() 相同(标准化)
100%
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" 按钮
按钮被鼠标悬停
鼠标离开按钮

交互流程:

  1. 输入框键入任务名,按回车或点"添加"按钮 → 任务加入列表
  2. 勾选复选框 → 任务标记完成(划掉文字 + 变灰)
  3. 点击 ✕ 按钮 → 删除该任务(stopPropagation 防止触发双击编辑)
  4. 双击任意任务 → 弹出编辑对话框

▶ 示例 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 版本共存时的冲突。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建一个 ButtonCounter 组件,每点击一次按钮,显示的计数值 +1。使用 onClick 事件。
  2. 进阶题(难度⭐⭐):创建一个 SearchInput 组件,实现"防抖搜索":用户停止输入 500ms 后自动触发搜索。使用 onChange 和 useEffect。
  3. 挑战题(难度⭐⭐⭐):创建一个 DragAndDropList 组件,实现拖拽排序功能。使用 onDragStart、onDragOver、onDrop 事件。列表项可拖动到新位置。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏