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 里,它依然能帮你写出更优雅也更专业的代码。

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

相关推荐
JZXStudio1 小时前
独立开发者亲测:MLX框架让我的App秒变AI原生!15年iOS老兵的2025新感悟
前端·ios
我叫黑大帅1 小时前
存储管理在开发中有哪些应用?
前端·后端·全栈
鲨叔1 小时前
zustand 从原理到实践 - 原理篇(2)
前端·react.js
Heo1 小时前
先把 Rollup 搞明白,再去学 Vite!
前端·javascript·面试
狐篱1 小时前
vite 和 webpack 项目使用wasm-pack 生成的 npm 包
前端·webassembly
閞杺哋笨小孩1 小时前
内容平台-SEO 索引提交
前端·seo
苏打水com1 小时前
HTML/CSS 核心考点详解(字节跳动 ToB 中台场景)
java·前端·javascript
jingling5551 小时前
react | 从零开始:使用 Create React App 创建你的第一个 React 项目
前端·javascript·react.js
nnnnna1 小时前
watch监听(一篇文章彻底搞懂watch监听)
前端·vue.js