事件代理-父监听事件获取的target始终保证为子组件最外层包裹标签

94 阅读2分钟

事件代理原理不多记录了,直接上代码来说明案例情况

比如有一个子组件

<div
    :class="['item', { 'active': index === curIndex}]"
    :data-index="index"
  >
    <i>
      <slot  name="icon"></slot>
    </i>
    <div class="details">
      <h3>
        <slot name="heading"></slot>
      </h3>
      <slot></slot>
    </div>
  </div>

子组件内部有icon区域,有header区域,有content区域,用户点击时,绑定在父组件上的代理事件,获取的target指向为这些区域的标签元素,而不是最外层的标签元素.

事件代理是利用事件冒泡的机制,将事件处理程序绑定到父元素,然后通过事件对象的target属性来判断实际触发事件的子元素。但是当子元素嵌套时,target可能会指向最内层的元素,也就是具体的icon区域,有header区域,有content区域,而不是外层的包裹标签,实际开发中,就会有个需求,无论用户点击的是包裹标签(例子中是有item类的div)内部的哪个子元素,都能获取到对应的包裹标签元素作为目标元素.

这里用到了 Element.closest() ,MDN的描述为:

Element.closest()  方法用来获取:匹配特定选择器且离当前元素最近的祖先元素(也可以是当前元素本身)。如果匹配不到,则返回 null

用在代理方法中,就是使用closest(),从当前点击元素对象上,开始向上查找匹配指定选择器的第一个祖先元素,这里的子组件外层有一个 item 类

const itemClick = function (e) {
  const tar = e.target.closest(".item");
  // const tar = findParent(e.target);
  if(tar) {
    const classList = tar.classList;
    if ( classList.contains('item') ) {
      const index = parseInt(tar.dataset.index);
      curIndex.value = index;
    }
  }
}

当然也可以自己写个方法,手动循环查找父元素

const findParent = function (el) {
  while (el && !el.classList.contains('item')) {
    el = el.parentElement;
    if (el === null) break; // 到达文档根元素时退出
  }
  return el;
}

通过上面的方法,来更好地在真实开发场景中应用事件代理