微信小程序事件委托:e.target 还是 e.currentTarget?一字之差导致筛选栏间歇失效
在一个小程序的列表页里,我做了一排筛选 tab(全部 / 待处理 / 已完成),用了典型的事件委托写法:父 <view> 挂一个 bindtap,三个子 <view> 各自带 data-filter。代码看起来干净利落,但上线后用户反映:点筛选有时候没反应,有时候又好的——间歇性 bug,最难追。
现象
用手指点 tab 文字正中间 → 筛选不触发;点 tab 的边缘空白 → 筛选正常切换。必现复现路径:从正中间点"全部"两字,console 打出 filter = undefined,setData 不执行。
根因
微信小程序事件对象有两个容易混淆的字段:
| 字段 | 指向谁 | dataset 来源 |
|------|--------|-------------|
| e.target | 手指真实落点的最内层节点(冒泡起点) | 该最内层节点的 data-* |
| e.currentTarget | 绑定 bindtap 的那个节点(handler 挂的位置) | handler 节点的 data-* |
问题就出在这里。我的 WXML 结构是:
<view class="tab-bar" bindtap="changeFilter">
<view class="tab-item" data-filter="all">全部(12)</view>
<view class="tab-item" data-filter="pending">待处理(5)</view>
<view class="tab-item" data-filter="done">已完成(7)</view>
</view>- 点 tab-item 边缘空白 →
e.target= tab-item →dataset.filter = "all"✅ - 点 tab-item 内部文字 → 文字节点被编译为内部 text 盒子,
e.target指向它 →dataset.filter = undefined❌
而 handler 里我写的是 e.currentTarget.dataset.filter——currentTarget 永远指向父级 tab-bar,父级根本没有 data-filter,结果同样是 undefined。
父级 bindtap + 子级 data-* 是委托模式,此时 currentTarget 指向没有 data-* 的父节点,永远拿不到值。正确做法是用 e.target.dataset——但 target 会在子节点内部还有嵌套(text/icon)时再次失效。两种姿势各有适用场景,搞混就是间歇 bug。
正解
模式 A(推荐):每个子节点独立绑定,用 currentTarget
<view class="tab-bar">
<view class="tab-item" bindtap="changeFilter" data-filter="all">全部</view>
<view class="tab-item" bindtap="changeFilter" data-filter="pending">待处理</view>
<view class="tab-item" bindtap="changeFilter" data-filter="done">已完成</view>
</view>changeFilter(e) {
const filter = e.currentTarget.dataset.filter; // ✅ bindtap 挂在 tab-item,currentTarget 就是它
if (filter && filter !== this.data.filter) {
this.setData({ filter });
}
}点击 tab-item 内部任何节点,事件都会冒泡到 tab-item,currentTarget 始终是它,dataset 稳定有值。
模式 B(简洁但脆弱):父级委托 + 用 e.target,加防御早退
changeFilter(e) {
const filter = e.target.dataset.filter; // 命中 tab-item 时有值,命中内部文字时 undefined
if (!filter) return; // ✅ 必须加这个早退,否则空值 setData 污染状态
if (filter !== this.data.filter) {
this.setData({ filter });
}
}模式 B 只适合子项是纯文本、无嵌套子节点的情形。一旦 tab 里加了 icon 或 <text> 包裹,命中内层节点时 e.target 就不再是 tab-item,又回到 undefined。可以在子节点上加 CSS pointer-events: none 强制让点击穿透到 tab-item,但这比较 hacky。
| tab-item 内部是否有嵌套 text/icon | 推荐模式 |
|----------------------------------|---------|
| 纯文本,无嵌套 | B(父委托 + e.target + if (!filter) return) |
| 含 icon 或 <text> 包裹 | A(子各自 bindtap + e.currentTarget) |
| wx:for 动态生成 | A,避免内部 DOM 演化引发漏 data |
同一文件里多个委托 handler 必须风格一致,复制粘贴时把模式 A 的 currentTarget 套到模式 B 的结构上,就是这次 bug 的直接原因。
排查 Checklist
- [ ]
bindtap挂在哪个节点?data-*挂在哪个节点?两者是否同一节点? - [ ] handler 里用的是
e.target还是e.currentTarget?与上一条匹配吗? - [ ] tab-item 内部是否有
<text>/<image>/ icon?命中它们时 dataset 还有值吗? - [ ] 是否写了
if (!filter) return防御父级空白区命中?
一句话外卖
currentTarget永远跟着bindtap走,target永远跟着手指走——委托模式的data-*挂在哪里,就用对应那侧的字段。