React 事件处理

用户点按钮、敲键盘、提交表单——这些"交互"怎么让组件响应?答案就是 React 的事件系统。它的写法和原生 DOM 很像,但有几个关键不同,这一章我们一次讲清。

1. 基本写法:驼峰命名 + 花括号

React 事件的写法和原生 HTML 几乎一样,两个区别:事件名用驼峰(onClick 不是 onclick),handler 用花括号传函数(不是字符串)。

// React 事件用驼峰命名:onclick → onClick,onchange → onChange
function Button() {
  const handleClick = () => {
    console.log("按钮被点了");
  };
  return <button onClick={handleClick}>点我</button>;
}

// 或者直接写箭头函数(短小逻辑时方便)
function Button() {
  return <button onClick={() => console.log("点了")}>点我</button>;
}

// 对比原生 DOM 写法:
// HTML: <button onclick="handleClick()">点我</button>
// React: <button onClick={handleClick}>点我</button>(注意没有括号)

注意一个常见错误:写 onClick={handleClick()}(带括号)会立刻执行函数,而不是点击时才执行。正确写法是 onClick={handleClick}(传函数本身),或用箭头函数包一层 onClick={() => handleClick()}

2. 合成事件对象

事件处理函数会自动收到一个参数 e,它是 React 的合成事件(SyntheticEvent)。它包装了原生事件,API 几乎一样,但帮你抹平了浏览器差异:

function Input() {
  // 第一个参数 e 是"合成事件"(SyntheticEvent)
  // 它包装了原生事件,API 几乎一样,但跨浏览器兼容
  const handleChange = (e) => {
    console.log("输入的内容:", e.target.value);
    // e.target 是触发事件的 DOM 元素(这里是 input)
  };

  const handleClick = (e) => {
    console.log("点击类型:", e.type);        // "click"
    console.log("鼠标坐标:", e.clientX, e.clientY);
    e.preventDefault();   // 阻止默认行为(如 a 标签跳转)
    e.stopPropagation();  // 阻止事件冒泡
  };

  return (
    <>
      <input onChange={handleChange} />
      <a href="https://example.com" onClick={handleClick}>
        不会跳转的链接
      </a>
    </>
  );
}

日常最常用的就是 e.target.value(输入框的值)、e.preventDefault()(阻止默认行为)、e.stopPropagation()(阻止冒泡)。表单提交的 onSubmit 一定要加 e.preventDefault(),否则页面会刷新,React 的 state 也全没了。

3. 给事件 handler 传参

这是新手最常卡住的地方:onClick 的 handler 默认只接收事件对象,但我想把列表项的 id 传进去怎么办?

function TodoList({ todos, onDelete }) {
  // ❌ 错误:这样会立刻执行,而不是点击时执行
  // <button onClick={onDelete(todo.id)}>删除</button>

  // ✅ 方式 1:用箭头函数包一层(最常用)
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
          <button onClick={() => onDelete(todo.id)}>删除</button>
        </li>
      ))}
    </ul>
  );
}

// ✅ 方式 2:用 .bind(事件对象会作为第二个参数传入)
<button onClick={onDelete.bind(null, todo.id)}>删除</button>

// ✅ 方式 3:用 data-* 属性(更接近原生写法)
<button data-id={todo.id} onClick={handleClick}>删除</button>
// 然后在 handleClick 里读 e.currentTarget.dataset.id

最常用的是箭头函数包一层:onClick={() => onDelete(todo.id)}。这样点击时才执行箭头函数,内部调用 onDelete 并传入 id。清晰、好读、不会出错。

4. 常用事件类型

React 支持几乎所有原生事件,常用的有:

function Form() {
  // click:鼠标点击
  const handleClick = () => console.log("点击");

  // change:输入框内容变化(每按一个键都触发)
  const handleChange = (e) => console.log(e.target.value);

  // submit:表单提交
  const handleSubmit = (e) => {
    e.preventDefault();   // ⚠️ 必加,否则页面会刷新
    console.log("提交表单");
  };

  // mouseEnter / mouseLeave:鼠标进入/离开(不冒泡版)
  const handleEnter = () => console.log("鼠标进来了");

  // focus / blur:获得/失去焦点
  const handleFocus = () => console.log("聚焦了");

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} onFocus={handleFocus} />
      <button onClick={handleClick} type="submit">提交</button>
      <div onMouseEnter={handleEnter}>悬停区域</div>
    </form>
  );
}

注意 onChange 在 React 里和原生不同:原生 input 的 change 是失焦才触发,React 的 onChange 是每次输入都触发(更像原生的 oninput)。这是 React 为了实时受控刻意设计的。

5. 合成事件 vs 原生事件

React 的事件绑定机制和原生有个重要区别:

实践中几乎感觉不到差别,但要知道为什么有时候 e.stopPropagation() 不生效——它只能阻止 React 合成事件的冒泡,挡不住原生监听器。

6. 综合示例:计数器

把事件和 state 组合起来,就是一个完整的交互组件:

import { useState } from "react";

// 经典例子:把事件、state 组合起来
function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(0);

  return (
    <div>
      <p>当前计数:{count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
      <button onClick={reset}>重置</button>
    </div>
  );
}

事件 → set state → 重渲染 → UI 更新,这就是 React 交互的完整闭环。任何复杂的 React 应用,本质都是这个循环的放大版。

小结

React 事件用驼峰命名 + 花括号传函数。事件对象是合成事件,常用 e.target.valuee.preventDefault()。给 handler 传参用箭头函数包一层。下一篇看 React 怎么渲染列表——列表与 key

← 上一篇 State 与 useState

下一篇 列表渲染与 key

✈️💬