Vue 3:我在真实项目中如何用事件委托

Vue 3:我在真实项目中如何用事件委托

上一篇冒泡带了一嘴事件循环,这一篇就来讲讲吧

在开发项目的时候,我其实很早就知道"事件冒泡""事件委托"这些概念,但真正让我对事件委托产生足够深刻理解,是在做一个动态的 Todo 列表模块时遇到的问题。当时我给每个列表项里的删除按钮都写了 @click="deleteItem(item.id)",结果列表一多、结构一复杂,我就发现维护变得越来越吃力。

后来我重构成事件委托,一下子清爽了许多,也顺便把事件冒泡、事件修饰符等机制彻底吃透了。所以,我在这篇文章里,把我真实的开发体验、踩过的坑、以及最后怎么通过事件委托解决问题,都记录下来。


一、我最开始的写法(未使用事件委托)

这是大部分初学者最熟悉的写法,也确实能工作:

html 复制代码
<ul ref="todoList" class="todo-list">
  <li v-for="item in todos" :key="item.id" :data-id="item.id">
    <span>{{ item.text }}</span>
    <!-- 每个按钮都独立绑定事件 -->
    <button class="delete" @click="deleteItem(item.id)">删除</button>
  </li>
</ul>

这个写法很直观,但当时我遇到了三个问题:

1. 列表稍微大一点,事件监听器就变多

一百条数据就有一百个 click 事件监听。Vue 会帮我做虚拟 DOM diff,但事件监听器还是被绑定到真实 DOM 上的。

2. 列表是动态的,每次增删都会重新创建 / 销毁事件监听器

我当时的 Todo 列表支持"从服务器滚动加载更多",每次追加数据就会追加更多按钮。

3. 每一个按钮都写 @click,模板开始变得臃肿

特别是当一个列表项里不止一个按钮(编辑、复制、删除、更多)的时候,模板根本不想看。

这些问题叠加起来,促使我开始考虑事件委托。


二、重构后的写法:使用事件委托

改用事件委托后,我只在父元素 <ul> 上写一个 @click

html 复制代码
<ul ref="todoList" class="todo-list" @click="onListClick">
  <li v-for="item in todos" :key="item.id" :data-id="item.id">
    <span>{{ item.text }}</span>
    <!-- 删除按钮不再绑定事件 -->
    <button class="delete">删除</button>
  </li>
</ul>

然后在逻辑里统一处理:

js 复制代码
function onListClick(event) {
  const target = event.target

  if (target.classList.contains('delete')) {
    const li = target.closest('li')
    const id = li.dataset.id
    deleteItem(id)
  }
}

改完以后,我当时立刻感受到模板轻松了许多,而且新增数据、删除数据完全不需要我关心事件绑定的问题了,全自动生效。


三、事件委托在 Vue 3 中依然必要吗?我真实的结论:必要,而且很好用

有人认为:既然 Vue 有虚拟 DOM,有组件系统,那是不是事件委托就没必要了?

但我真实项目经验告诉我:事件委托依然非常有价值。

1. Vue 事件绑定再智能,也无法帮你减少真实 DOM 的监听器数

虚拟 DOM 再聪明,真实 DOM 上有多少监听器,那是实打实的。

事件委托减少的是浏览器层面的监听器数量,Vue 无法替你做这个优化。

2. 动态列表(频繁增删)时,委托是最稳的方案

我当时的列表是"不断加载更多",如果用子节点绑定事件:

  • 每次新增数据 → 新增监听器
  • 每次删除数据 → 删除监听器

委托只需要一个监听器。

3. 多个按钮、多种操作时,委托逻辑比模板绑定更清晰

如果有:编辑 / 复制 / 分享 / 删除 ...

模板这样写真的很乱:

html 复制代码
<button @click="edit(item)">编辑</button>
<button @click="copy(item)">复制</button>
<button @click="share(item)">分享</button>
<button @click="delete(item)">删除</button>

但委托只需要一个判断:

js 复制代码
if (target.classList.contains('edit')) { ... }
if (target.classList.contains('copy')) { ... }
if (target.classList.contains('share')) { ... }
if (target.classList.contains('delete')) { ... }

这在复杂交互中特别爽。


四、顺带讲讲:事件捕获 / 冒泡 / 修饰符(.stop .prevent .self)到底什么时候用?

在写事件委托时,我顺便把 Vue 的事件修饰符用得很熟,这里总结一下。

1)事件冒泡(bubbling)

事件从目标元素向上冒泡到父元素、再到根。

事件委托正是利用了冒泡。

2)事件捕获(capturing)

事件从外往内传播(一般很少用)。

Vue 里可用:

ini 复制代码
@click.capture="handle"

但除非做特殊行为,一般不用。

3).stop ------ 阻止冒泡

js 复制代码
@click.stop="deleteItem"

这会阻止事件继续冒泡到 <ul>

在事件委托中,有些按钮可能不希望触发 onListClick,就可以用它。

4).prevent ------ 阻止默认行为

如阻止 <a> 的跳转:

js 复制代码
@click.prevent="openModal"

5).self ------ 仅当点到自身时触发

js 复制代码
@click.self="closeDialog"

不会因为点击子元素而触发。

这个我在 dialog 遮罩层里用过。


五、我的最终感受

事件委托是一个看似简单却非常实用的技巧:

  • 模板更干净
  • 父级统一管理事件更好维护
  • 动态列表也无需重新绑定事件
  • 性能更轻量

在 Vue 3 里,它依然能帮你写出更优雅也更专业的代码。

如果你的项目里也有大量列表、按钮、表格类结构,我非常建议你尝试一次事件委托 ------ 你可能会像我一样,写完之后再也回不去过去那种"按钮上绑一堆事件"的方式了。

相关推荐
程序员黑豆2 分钟前
鸿蒙应用开发教程:以红绿灯切换为例,掌握条件渲染的核心用法
前端·harmonyos
猫32810 分钟前
echarts 日常问题解决
前端·javascript·echarts
新中地GIS开发老师13 分钟前
地信职业百科④:GIS开发工程师
前端·数据库·gis·webgis·三维gis开发
我是大卫22 分钟前
【图】React源码解析-从数据结构、调度器、源码设计模式到状态计算引擎,深挖useReducer原理
前端·react.js·源码
willes29 分钟前
列表中的倒计时组件
前端
LVZ31 分钟前
用了半年 AI 写代码,最烦的不是代码,是环境
前端·后端·开源
polaris_tl32 分钟前
一行 `compress: false`,为什么让 SSE 首包恢复实时返回
前端
海边的云33 分钟前
别再让 AI 瞎改代码:一套让大模型「收敛」的前端专家 Skill》
前端
kisshyshy33 分钟前
给端侧大模型装上“发动机”:React 合成事件 + 进度条组件全解
前端·react.js·node.js
hunterandroid1 小时前
DataStore 工程化实践:迁移、并发更新与异常恢复
android·前端