什么时候使用 stopPropagation() 是好的做法?

When is good practice to use stopPropagation()?

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

这真的不应该是一个答案,但是你只能在一个评论中写这么多


我认为您的问题在标题中包含 "good practice" 字样是不公正的。这种暗示在大多数情况下, stopPropagation 是不好的做法。这类似于说 eval 是邪恶的 。它以错误的教条主义完全抹杀了它的任何合法用例。

我从未发现自己处于使用 stopPropagation 感觉不像是避免解决实际问题的解决方法的情况。

在理想的世界中,应用程序是由较小的组件构建的,这些组件本身做的事情很少,但具有高度的可重用性和可组合性。为此,方法很简单但执行起来却非常困难:每个组件都必须对外部世界一无所知。

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

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

的事件

您还应该考虑这样一个事实,即阻止事件传播可能会导致其他组件行为异常。经典示例是 drop-down,当您在其外部单击时它会关闭。如果该点击停止,您的 drop-down 可能永远不会关闭。

将事件视为数据源。您不想丢失数据。 相反!随它去,随它自由;)

虽然我不认为使用 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 仅在点击实际源自节点本身而不是其 children.

之一时才会执行

The currentTarget read-only property of the Event interface identifies the current target for the event, as the event traverses the DOM. It always refers to the element to which the event handler has been attached, as opposed to Event.target, which identifies the element on which the event occurred.

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

方案二:使用事件委托

您实际上不需要三个事件处理程序。只需在 #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>