R07第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

事件处理与传播

事件处理与传播 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

交互写多了就会遇到「点一个按钮,两件事同时发生」:弹窗里的删除按钮把遮罩也关了,卡片上的小按钮把卡片也选中了。这一课讲清 React 事件模型:处理器怎么绑、事件怎么传播、表单提交的正确时机。读完你能拆掉这类误触发的连锁反应。

问题场景

课程卡片整块可点击(点击选中),卡片右下角又放了一个「删除」按钮:

<div className="card" onClick={() => select(course.id)}>
  <h3>{course.title}</h3>
  <button onClick={() => remove(course.id)}>删除</button>
</div>

点「删除」,卡片先被选中然后又被删掉——选中逻辑根本不该执行。原因是按钮在卡片内部:点击按钮时,事件沿着 DOM 树向上冒泡,先触发按钮自己的 onClick,再触发外层卡片的 onClick。两个处理器都执行了。

核心解释

React 的事件模型有三个要点:

一、绑定与命名。 JSX 里用驼峰属性绑事件:onClick、onChange、onSubmit,值是函数而不是字符串。React 把原生事件包了一层合成事件(SyntheticEvent),用法和原生一致:e.preventDefault() 阻止默认行为,e.stopPropagation() 阻止继续冒泡。

二、传播。 浏览器事件默认从最深的目标元素开始逐层向外冒泡。React 的 onClick 挂在冒泡阶段,所以「内层先执行、外层后执行」。想在内层截断,就在内层处理器里调 e.stopPropagation():

<button
  onClick={(e) => {
    e.stopPropagation(); // 事件不再冒泡到卡片
    remove(course.id);
  }}
>
  删除
</button>

stopPropagation 不是随手加的保险丝——它会让外层完全感知不到这次点击。滥用它,其他依赖冒泡的全局逻辑(点击空白处关闭弹窗)就会失灵。正确姿势是:只在「内层动作与外层动作语义无关」时使用。

三、表单提交时机。 表单的回车键和提交按钮都会触发 submit 事件,正确入口只有一个:

<form onSubmit={(e) => { e.preventDefault(); submit(); }}>
  <input />
  <button type="submit">搜索</button>
</form>

preventDefault 阻止浏览器整页刷新,submit 保证「点按钮」和「按回车」走同一条路,不会漏分支。

示例拆解

实验区把三个易错场景摆在同一页,关键点如下:

  • 卡片与删除按钮:切换「阻止冒泡」开关后,点删除按钮的行为不同——关闭时右侧日志显示「删除 + 选中卡片」两条记录,开启时只有「删除」;
  • 事件日志面板:每次触发处理器就追加一条带序号的记录,冒泡顺序(内层先、外层后)肉眼可见;
  • 搜索表单:onSubmit 统一处理「点提交」和「输入框按回车」两种触发方式,日志里都是同一个「提交」事件;输入框内的回车不会误触外层卡片——事件目标是输入框,冒泡路径不经过卡片的 onClick 所在元素(表单在卡片外);
  • 提交后的默认行为被 preventDefault 拦下,页面没有刷新(刷新会丢掉整个实验状态)。

验证方式:开关切换前后各点一次删除按钮,对比日志条数。

动手任务

打开本课实验区,完成三个小改动:

  1. 给卡片加一个「收藏」按钮,同样阻止冒泡,验证选中逻辑不再误触发。
  2. 把表单提交按钮改成 type="button",观察提交不再触发——理解 type 的默认值是 submit。
  3. 在日志面板里加一条时间戳,观察同一连串触发的时间间隔,确认都是同一瞬间完成(不是异步排队)。

常见错误

  • 忘记 preventDefault:表单提交后页面整页刷新,所有状态归零;onSubmit 第一行就写上。
  • 内外层处理器语义耦合:外层靠「没拦住」才不执行动作,内层一改逻辑就连锁爆炸;该拦就显式 stopPropagation。
  • 按钮没写 type:<button> 默认 type="submit",放在 form 里点它会顺手触发提交;非提交按钮写 type="button"。
  • 用 onClick 监听回车:在 input 上挂 onKeyDown 判断 Enter 而不用表单 onSubmit,会漏掉「点提交按钮」这条路;提交统一交给 onSubmit。

检查与复盘

完成下方检查题确认理解。自查清单:

  • 能画出一个三层嵌套结构的冒泡路径,说出处理器执行顺序
  • 知道 stopPropagation 和 preventDefault 各管什么、什么时候用
  • 表单交互统一走 onSubmit,按钮显式声明 type

参考资料

动手实验
动手实验
预览
点击「运行」在隔离沙箱中预览
检查题共 2 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选简单

表单写的是 <form onSubmit={handleSubmit}>,点击提交按钮后页面整页刷新,输入内容全部丢失。最可能的原因是?

Q2读代码中等

阅读代码(React 18+)。点击内层的「保存」按钮后,log 里依次出现哪些文字?

function Toolbar() {
  const [log, setLog] = useState([]);
  const push = (msg) => setLog((l) => [...l, msg]);

  return (
    <div onClick={() => push('toolbar')}>
      <div
        onClick={(e) => {
          e.stopPropagation();
          push('inner');
        }}
      >
        <button onClick={() => push('save')}>保存</button>
      </div>
    </div>
  );
}
// 页面同时把 log 逐项渲染出来

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...