事件委托(Event Delegation)的原理

优化前的问题

原来的代码是这样的:

TSX 复制代码
{allCategories.map((category) => (
  <div
    key={category.id}
    className={styles.categoryItem}
    onClick={() => onCategoryChange(category.slug)}  // ❌ 每个 item 都创建新函数
  >
    {/* ... */}
  </div>
))}

问题分析:

  1. 每次渲染都会为每个分类项创建一个新的箭头函数:() => onCategoryChange(category.slug)
  1. 假设有 20 个分类,就会创建 20 个函数,这些函数会形成闭包,占用内存
  1. 每次组件重新渲染时,React 会认为 onClick 是一个新的函数引用,可能导致不必要的子组件重新渲染
  1. 虽然通常不是性能瓶颈,但在分类数量很多时,这会造成不必要的开销

二、事件委托(Event Delegation)的原理

事件委托将事件处理函数绑定到父容器上,而不是每个子元素。利用事件冒泡,点击子元素时,事件会冒泡到父容器,父容器的事件处理函数接收事件,并根据目标元素执行相应逻辑。事件冒泡示意图:

TEXT 复制代码
点击子元素 → 事件冒泡 → 父容器捕获事件 → 根据 target 判断处理逻辑

三、优化后的实现

1. 在父容器上绑定统一的事件处理函数

TSX 复制代码
 // 处理分类列表点击 - 使用事件委托
  const handleCategoryListClick = (e: React.MouseEvent<HTMLDivElement>) => {
    const target = e.target as HTMLElement;
    // 向上查找最近的 categoryItem 元素
    const categoryItem = target.closest(`.${styles.categoryItem}`);
    if (categoryItem) {
      const categorySlug = categoryItem.getAttribute('data-category-slug');
      if (categorySlug) {
        onCategoryChange(categorySlug);
      }
    }
  };

函数执行流程:

  1. e.target 获取被点击的具体元素(可能是图标、文字等)
  1. closest() 向上查找最近的 .categoryItem 元素
  1. 从该元素读取 data-category-slug 属性
  1. 调用 onCategoryChange(categorySlug)

2. 在父容器上绑定事件

HTML 复制代码
      <div className={styles.categoryList} onClick={handleCategoryListClick}>

3. 为每个 item 添加 data 属性

ini 复制代码
  data-category-slug={category.slug}

使用 data-* 属性存储分类标识,符合 HTML5 规范。

四、代码执行流程示例

假设用户点击了"热门工具"分类项:

TEXT 复制代码
1. 用户点击 "热门工具" 文字
   ↓
2. 事件冒泡到 <div className={styles.categoryList}>
   ↓
3. handleCategoryListClick 函数被触发
   ↓
4. e.target = <span className={styles.categoryName}>热门工具</span>
   ↓
5. target.closest('.categoryItem') 向上查找
   → 找到 <div className={styles.categoryItem} data-category-slug="hot">
   ↓
6. categoryItem.getAttribute('data-category-slug') 
   → 返回 "hot"
   ↓
7. 调用 onCategoryChange("hot")
   ↓
8. 完成分类切换

五、为什么使用 closest()?

因为点击可能发生在 item 内部的任意元素上(图标、文字等)。closest() 可以向上查找最近的匹配元素:

HTML 复制代码
<div className={styles.categoryItem} data-category-slug="hot">
  <div className={styles.categoryContent}>
    <div className={styles.categoryIcon}>ICON</div>  // ← 可能点击这里
    <span>热门工具</span>                          // ← 也可能点击这里
  </div>
</div>

无论点击图标还是文字,closest() 都能找到外层的 categoryItem。

### 六、其他优化方案的对比

  1. 创建单独的子组件 + useCallback
  • 优点:封装性好
  • 缺点:增加了组件层级和复杂度
相关推荐
仿生狮子4 小时前
OpenAI 攻下千禧年难题之后,开发者似乎无动于衷
前端·数学·vibecoding
Lvan的前端笔记7 小时前
docker:每个前端项目一个 Nginx 容器还是只有一个Nginx容器
前端·nginx·docker
三天不学习7 小时前
Egg.js 4 突然爆火,原因是否归结于AI 原生落地需求爆发?
前端·javascript·全栈·egg.js
xcs194058 小时前
前端 vue 的前端页面debugger 进不去
前端·javascript·vue.js
明月_清风9 小时前
Deno 终局来了:从挑战 Node 到被 Cloudflare 收编
前端·后端·node.js
Csvn10 小时前
框架性能优化
前端
回眸&啤酒鸭12 小时前
【回眸】OpenSwarm 多智能体协作系统实战指南
大数据·前端·人工智能
用户693717500138412 小时前
2026,程序员的时代拐点到了
android·前端·后端
大龄秃头程序员12 小时前
一次 iBeacon + BLE 无感解锁方案的实现记录
前端
小兔子12 小时前
Python 的 GIL 与 free-threading:3.13 之后「去 GIL」走到哪一步了
前端