人人都会AI编程

3.3 列表渲染:v-for 遍历、key 属性规范

更新时间:2026-07-10

React 的事件处理机制与原生 DOM 事件有相似之处,但包裹了一层合成事件(SyntheticEvent),抹平了浏览器之间的差异,并统一了事件对象的行为。掌握 React 中的事件绑定、参数传递和事件对象用法,是编写交互逻辑的基础。

事件绑定:用 JSX 属性直接监听

React 使用驼峰式属性名绑定事件,比如 onClickonChangeonSubmit 等。事件处理函数通常以 handle 开头,这是社区约定。

function Button() {
  function handleClick() {
    console.log('按钮被点击了');
  }

  return <button onClick={handleClick}>点击我</button>;
}

关键规则:

  • 传入的是函数引用(handleClick),而不是函数调用(handleClick())。如果写成 onClick={handleClick()},函数会在渲染时立即执行,这通常不是你想要的行为。
  • 事件处理函数可以定义为组件内部的普通函数,也可以使用箭头函数。使用箭头函数的好处是可以自动绑定当前词法作用域中的变量(避免 this 困扰,但在函数组件中无需考虑 this)。

传递参数:箭头函数或柯里化

实际开发中经常需要在触发事件时传递额外参数,比如列表某一项的 id。有三种常用方式:

方式一:内联箭头函数(最常用)

function ItemList({ items }) {
  function handleDelete(id) {
    console.log('删除项目:', id);
  }

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.name}
          <button onClick={() => handleDelete(item.id)}>删除</button>
        </li>
      ))}
    </ul>
  );
}

箭头函数 () => handleDelete(item.id) 创建了一个新函数,当点击时它才去调用 handleDelete 并传入 item.id。这是最直观的方式,但每次渲染都会生成新函数,如果 item 数量很大且无需优化,一般性能影响可忽略。若出现性能瓶颈,可结合 useCallback 或通过数据属性优化。

方式二:使用 bind 绑定参数

<button onClick={handleDelete.bind(null, item.id)}>删除</button>

bind 会返回一个新的函数,预设了第一个参数。但可读性不如箭头函数,社区较少采用。

*方式三:通过自定义属性 data- 间接传参**

function handleDelete(e) {
  const id = e.currentTarget.dataset.id;
  console.log('删除项目:', id);
}

<button data-id={item.id} onClick={handleDelete}>删除</button>

这种方式避免了每次渲染生成新函数,适用于对性能敏感的极大量列表(但多数情况直接使用箭头函数足够清晰)。事件处理函数通过 e.currentTarget.dataset 读取参数。

事件对象:合成事件与原生事件

React 传递给事件处理函数的第一个参数是 SyntheticEvent(合成事件),它是对浏览器原生事件的跨浏览器封装,提供了统一的接口。

function Input() {
  function handleChange(e) {
    // e 是 SyntheticEvent
    console.log(e.target.value);   // 触发事件的元素
    console.log(e.type);           // 事件类型,如 "change"
  }

  return <input onChange={handleChange} />;
}

常用属性和方法:

  • e.target:触发事件的 DOM 元素(比如用户实际点击的按钮)。
  • e.currentTarget:事件绑定的元素(事件处理函数所附加的元素)。在 React 中由于事件委托,e.currentTarget 通常与 e.target 一致。
  • e.type:事件类型(如 clickchange)。
  • e.preventDefault():阻止默认行为(如表单提交刷新页面)。
  • e.stopPropagation():阻止事件冒泡。

重要:React 事件的异步访问与持久化

React 17 之前,合成事件对象在事件处理函数执行完毕后会被回收(出于性能考虑),所以无法在异步函数(如 setTimeoutPromise)中直接访问事件对象。React 17 之后这一行为已变更,不再需要手动调用 e.persist(),合成事件不会立即置空。但在旧项目中仍可能遇到旧行为,了解即可。

// React 16 及之前,异步访问需要调用 e.persist()
function handleClick(e) {
  e.persist(); // 保留事件对象
  setTimeout(() => {
    console.log(e.target); // 否则这里会是 null
  }, 1000);
}
// React 17+ 不再需要 e.persist()

阻止默认行为与冒泡

在表单提交、链接跳转等场景,需要阻止浏览器的默认行为。使用 e.preventDefault() 即可。

function Form() {
  function handleSubmit(e) {
    e.preventDefault();        // 阻止页面刷新
    const formData = new FormData(e.currentTarget);
    console.log(Object.fromEntries(formData));
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="username" />
      <button type="submit">提交</button>
    </form>
  );
}

有时需要阻止事件冒泡到父元素,使用 e.stopPropagation()。注意区分:

  • e.stopPropagation() 阻止事件向上冒泡,但不阻止当前元素的其他处理函数。
  • e.preventDefault() 阻止默认行为,但事件仍会冒泡。

常见事件类型速查

| 事件名 | 典型元素 | 说明 |
|--------|---------|------|
| onClick | 任何元素 | 鼠标单击 |
| onChange | <input><select><textarea> | 值改变时触发(React 中表现接近原生 input 事件) |
| onSubmit | <form> | 表单提交 |
| onFocus / onBlur | 可聚焦元素 | 获得/失去焦点 |
| onKeyDown / onKeyUp / onKeyPress | 可聚焦元素 | 键盘事件 |
| onMouseEnter / onMouseLeave | 任何元素 | 鼠标进出(不冒泡) |
| onScroll | 滚动容器 | 滚动事件 |

实际开发中的注意事项

  1. 避免在 JSX 中使用内联函数时造成不必要的重渲染:当组件比较庞大且 onClick={() => handler(param)} 作为 Props 传给子组件时,可能会破坏子组件的 React.memo 优化,因为每次父组件渲染都会生成新的函数引用。此时可以使用 useCallback 或者子组件配合 data-* 属性来接收参数。
  1. onChange 中更新状态要记得受控:如果输入框的 value 绑定了状态,则必须提供 onChange 来更新状态,否则输入框将无法输入。
  1. 事件处理函数内部可以访问最新 state 吗? 由于闭包,如果你在事件处理函数中使用了 state 但没有正确处理依赖,可能会读到过时的值。在函数组件中,只要状态更新不是被异步打乱顺序,通常事件触发时 state 已是最新(因为事件处理函数是在渲染时注册的最新版本)。遇到闭包陷阱时,可以使用函数式更新 setState(prev => ...) 来基于最新状态计算。

React 的事件系统让跨浏览器兼容变得透明,但也需要你理解其合成事件的特性和参数传递的恰当方式,才能写出稳健的交互逻辑。