什么时候可以使用stopPropagation()?

时间:2019-02-21 20:09:47

标签: javascript event-handling dom-events stoppropagation event-propagation

有人尝试回答这个问题: hereherehere。但是,没有一个答案能给出可靠的答复。我不是在介绍event阶段capturebubbletarget以及stopPropagation()如何影响整个事件。我正在寻找将stopPropagation()添加到DOM节点将使整体代码受益的情况?

1 个答案:

答案 0 :(得分:4)

这确实不应该是一个答案,但是您只能在一个注释中写很多东西


我认为您的标题中没有“良好做法”一词,这是对您的质疑。这种暗示意味着在大多数情况下,stopPropagation是不好的做法。这类似于说 eval是邪恶的。它完全消除了放任主义的合法使用案例。

我从来没有发现自己在使用stopPropagation的情况下无法避免实际问题的解决方法。

在理想的世界中,应用程序是由较小的组件构建而成的,这些组件本身很少做,但是具有很高的可重用性和可组合性。要使此方法有效,该方法很简单,但很难执行:每个组件都必须对外界一无所知。

因此,如果某个组件需要使用stopPropagation(),那只能是因为它知道链中更深的东西会中断,或者会使您的应用程序进入不良状态。

在这种情况下,您应该问自己这是否不是设计问题的征兆。也许您需要一个协调和管理其子事件的组件?

您还应该考虑以下事实:阻止事件传播会导致其他组件行为异常。经典示例是一个下拉列表,当您在其外部单击时会关闭。如果该点击停止,则您的下拉列表可能永远不会关闭。

将事件视为数据源。您不想丢失数据。 互惠生!放开,放开它;)

虽然我不认为使用stopPropagation是坏事,但我认为不需要。


示例:如何避免使用stopPropagation

在此示例中,我们正在构建一个非常简单的游戏:如果您单击红色区域,您将输,而在绿色区域中,您将赢。单击鼠标即可结束游戏。

const onClick = (selector, handler) => {
  document.querySelector(selector).addEventListener('click', handler);
};

onClick('#game', () => console.log('game over'));
onClick('#red', () => console.log('you lost'));
onClick('#green', () => console.log('you won'));
#red, #green { width: 50px; height: 50px; display: inline-block; }
#red { background-color: orangered; }
#green { background-color: yellowgreen; }
<div id="game">
  <div id="red"></div>
  <div id="green"></div>
</div>

现在让我们想象一下,红色和绿色块随机排列在不同的层次上。在第42级,红色块包含绿色块。

const onClick = (selector, handler) => {
  document.querySelector(selector).addEventListener('click', handler);
};

onClick('#game', () => console.log('game over'));
onClick('#red', () => console.log('you lost'));
onClick('#green', () => console.log('you won'));
#red, #green { max-width: 100px; padding: 10px; }
#red { background-color: orangered; }
#green { background-color: yellowgreen; }
<div id="game">
  <div id="red">
    <div id="green"></div>
  </div>
</div>

如您所见,当您单击绿色区域时,您同时输赢!而且,如果您要向绿色处理程序中发出stopPropagation()调用,将无法赢得这场比赛,因为点击不会冒泡到游戏处理程序上来表示游戏结束!

解决方案1:确定点击的来源

const filter = handler => ev =>
  ev.target === ev.currentTarget ? handler(ev) : null;

const onClick = (selector, handler) => {
  document.querySelector(selector).addEventListener('click', handler);
};

onClick('#game', () => console.log('game over'));
onClick('#red', filter(() => console.log('you lost')));
onClick('#green', () => console.log('you won'));
#red, #green { max-width: 100px; padding: 10px; }
#red { background-color: orangered; }
#green { background-color: yellowgreen; }
<div id="game">
  <div id="red">
    <div id="green"></div>
  </div>
</div>

键功能是filter。它将确保仅在单击实际上源自节点本身而不是其子节点之一的情况下,handler才会执行。

  

当事件遍历DOM时,Event接口的currentTarget只读属性标识该事件的当前目标。它始终是指事件处理程序所附加的元素,而不是Event.target,后者标识发生事件的元素。

https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget

解决方案2:使用事件委托

您实际上不需要三个事件处理程序。只需在#game节点上设置一个即可。

const onClick = (selector, handler) => {
  document.querySelector(selector).addEventListener('click', handler);
};

onClick('#game', (ev) => {
  if (ev.target.id === 'red') {
    console.log('you lost');
  } else if (ev.target.id === 'green') {
    console.log('you won');
  }
  console.log('game over');
});
#red, #green { max-width: 100px; padding: 10px; }
#red { background-color: orangered; }
#green { background-color: yellowgreen; }
<div id="game">
  <div id="red">
    <div id="green"></div>
  </div>
</div>