事件委托(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
  • 优点:封装性好
  • 缺点:增加了组件层级和复杂度
相关推荐
qq_4221525717 分钟前
PDF页面管理的几种实用方法:拆分、合并、删除指定页面
java·前端·pdf
qq_2128389721 分钟前
后端Node.js + MySQL,前端Html+js,Nginx反向代理快速部署WEB项目
前端·mysql·node.js
Mh24 分钟前
程序员和非程序员使用 AI 进行编程的区别究竟在哪里?
前端·javascript·xcode
谭光志9 小时前
Vibe Coding 时代,程序员该何去何从
前端·后端·ai编程
仿生狮子11 小时前
别再说“全栈”了,AI 时代团队只认这 5 种人
前端·人工智能·后端
kyriewen11 小时前
AI 写的 React 组件,合并前必查的 6 个坏味道——每个都有代码对比
前端·javascript·react.js
Highcharts.js11 小时前
软件开发公司为什么选择 Highcharts?
前端·前端框架·echarts·数据可视化·技术选型·highcharts·图表工具
阿祖zu11 小时前
企业 AI 转型之痛,个人提效十倍不止,组织效率仍止步不前?
前端·aigc·agent
摇滚侠13 小时前
SpringBoot3+Vue3 全套视频教程 45-51
前端·javascript·vue.js
酸梅果茶13 小时前
【6】lightning_lm项目-LIO 前端 -点云预处理模块
前端·slam