"事件委托"这个老古董,在现代React/Vue里还有用武之地吗?

我刚入行那会儿,还是jQuery的时代。当时面试,事件委托(Event Delegation) 几乎是必考题。能用.on()方法,在一个父元素上,优雅地处理动态添加的子元素的事件,是衡量一个前端是否会玩的重要标准。

但快十年过去了,我们现在天天都在写React和Vue。我们习惯了在JSX或template里,直接给元素绑定onClick,像这样:

jsx 复制代码
<button onClick={handleClick}>Click Me</button>

这太方便了,以至于我们几乎忘了事件委托的存在。

那么,问题来了:事件委托这个听起来有点老古董的技术,在2025年的今天,还有用武之地吗?还是说,它已经被现代框架彻底淘汰了?

今天,我想聊聊我的看法。答案可能有点反直觉:

它不仅有用,而且你可能每天都在不知不觉地使用它。


什么是事件委托?

在深入框架之前,我们快速花一分钟,回顾一下什么是事件委托。

事件委托的核心,是利用了浏览器事件模型的 事件冒泡(Event Bubbling)机制。

传统方式 :如果你有一个包含100个<li>的列表,你需要给每个<li>都绑定一个点击事件,那就在内存中创建了100个事件监听器。

事件委托 :你只需要在它们的父元素<ul>上,绑定一个 点击事件。当用户点击任何一个<li>时,这个点击事件会像气泡一样,冒泡到父元素<ul>上,被<ul>的监听器捕获。

它的好处显而易见:

  1. 性能:内存中只有一个监听器,而不是100个,性能开销小。
  2. 动态性 :即使你后续通过JS,动态地往<ul>里新增了第101个<li>,这个点击事件依然对它生效,无需重新绑定。

来看一个原生JS的经典实现:

javascript 复制代码
<ul id="my-list">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>
javascript 复制代码
const list = document.getElementById('my-list');

list.addEventListener('click', function(event) {
  // event.target 是用户实际点击的那个元素
  if (event.target && event.target.matches('li')) {
    console.log('Clicked on item:', event.target.textContent);
  }
});

可是 React/Vue已经在帮你做事件委托了

好了,回到我们最初的问题。在React/Vue里,我们还需要自己去写上面那样的代码吗?

绝大部分情况下,不需要。因为框架已经做得更好了。

当你像下面这样,给100个按钮都写上onClick时,你以为React真的在每个DOM按钮上都调用了addEventListener吗?

jsx 复制代码
function ButtonList() {
  const items = Array.from({ length: 100 }, (_, i) => ({ id: i, name: `Button ${i}` }));

  return (
    <div>
      {items.map(item => (
        <button key={item.id} onClick={() => console.log(item.id)}>
          {item.name}
        </button>
      ))}
    </div>
  );
}

答案是:没有。React并没有那么傻。

它在底层实现了一套非常强大的 合成事件系统 (Synthetic Event System),而这套系统的核心,就是全局的事件委托

简单来说,React的工作原理是: 对于绝大部分事件(比如click, input, keydown等),React并不会把事件处理器直接绑定在你写的DOM元素上。

相反,它会在应用的根节点 (比如document或者#root),只绑定一个统一的事件监听器。

当你在某个按钮上点击时,这个原生的click事件会一路冒泡到根节点,被React的统一监听器捕获。然后,React会根据event.target等信息,去查找你点击的这个DOM元素,对应的是哪个React组件,并找到你写在组件上的onClick处理器,最后再执行它。

Vue也采用了类似的策略。

所以,事件委托并没有过时。它只是被我们的框架内化了,成为了我们享受到的、高性能体验的幕后。 你写的每一个onClick,都在享受着事件委托带来的红利,而无需关心其实现细节。


那我们自己,还有必要手写事件委托吗?

既然框架都帮我们做好了,那是不是意味着我们就可以彻底忘记这个技术了?

也不全是。在95%的情况下,你可以完全信赖框架。但在剩下5%的极限或特殊场景下,手动实现事件委托,依然是一个强大的工具。

超高频事件的性能优化

想象一个有数千个单元格的在线表格,或者一个数据可视化应用。每个单元格都需要响应mouseover事件来显示高亮或Tooltip。

如果你给这几千个单元格,都绑定一个React的onMouseOver,即使React有事件委托,在合成事件的分发和处理层面,当鼠标快速移动时,也可能会产生一定的性能开销。

在这种极限情况下,我们可以绕过React的合成事件系统,回归原生:

jsx 复制代码
function HighPerformanceGrid() {
  const gridRef = useRef(null);

  useEffect(() => {
    const gridElement = gridRef.current;
    if (!gridElement) return;
    
    const handleMouseOver = (event) => {
      // 直接操作DOM,不触发React的re-render
      if (event.target && event.target.matches('.grid-cell')) {
        event.target.style.backgroundColor = 'lightgray';
      }
    };
    
    // 手动在父元素上进行事件委托
    gridElement.addEventListener('mouseover', handleMouseOver);
    
    return () => {
      gridElement.removeEventListener('mouseover', handleMouseOver);
    };
  }, []);

  return <div ref={gridRef}>{/* 渲染成千上万个 .grid-cell 元素 */}</div>;
}

这种方式,性能几乎是最高的,因为它完全是原生DOM和事件的处理,避免了React的虚拟DOM diff和组件渲染流程。

操作不受框架控制的DOM

当你需要在一个React组件里,去管理一个由dangerouslySetInnerHTML渲染的、或者由某个第三方非React库生成的、不受React控制的DOM结构时,你无法在那些DOM元素上使用onClick。(尤其是 echarts, mapbox 等第三方Popup弹窗实现时非常有用)

此时,在这些DOM的父容器上,手动进行事件委托,就是最理所当然、也是唯一的选择。


事件委托是web前端开发的基础技能。

在现代框架的时代,我们可能不再需要天天去手写它。但它能让你更深刻地理解框架的运行原理,也能让你在遇到棘手的性能问题时,多了一个解决办法。

所以,下次当有新人问你事件委托还有没有用时,你可以告诉他:

它不仅有用,而且你可能天天都在用,只是React/Vue在替你负重前行

谢谢大家😁

相关推荐
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
吹牛不交税3 小时前
admin.net-v2 框架使用笔记-netcore8.0/10.0版
vue.js·.netcore