事件委托(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
  • 优点:封装性好
  • 缺点:增加了组件层级和复杂度
相关推荐
naildingding10 分钟前
Vue基础核心
前端·vue.js
弱鸡前端12 分钟前
纯前端实现pdf从生成到下载
前端
明月_清风19 分钟前
TanStack + Cloudflare 边缘实战:从 0 到 1 构建全栈应用
前端·全栈
东风破_19 分钟前
你天天用的 Python dict,90% 的人没搞懂这三个坑
前端
前端Hardy22 分钟前
21.8 万周下载!这个 React 表格组件,10 行代码就能跑起来
前端·javascript·后端
lichenyang45324 分钟前
# 鸿蒙 ArkTS 聊天 Demo 功能复盘:真实 SSE、多轮会话、暂停输出、历史记录与防崩溃修复 > 项目:`harmony-chat-demo`
前端
陈_杨27 分钟前
鸿蒙APP开发-带你走进胶片录的拍摄记录管理
前端·javascript
陈_杨33 分钟前
鸿蒙APP开发-带你走进胶片录的相机控制
前端·javascript
陈_杨35 分钟前
鸿蒙APP开发-带你走进节流战的Canvas图表
前端·javascript
陈_杨36 分钟前
鸿蒙APP开发-带你走进光绘记的拍摄规划
前端·javascript