什么时候使用 stopPropagation() 的好习惯?

Kle*_*leo 5 javascript event-handling event-propagation dom-events stoppropagation

已经有一些尝试来回答这个问题: hereherehere。然而,没有一个答案给出了可靠的回应。我不是指的event阶段capturebubble以及target如何stopPropagation()影响整个事件。我正在寻找添加stopPropagation()到 DOM 节点将有利于整体代码的情况?

cus*_*der 11

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


我认为您在标题中使用“良好实践”一词并没有公正地对待您的问题。这意味着在大多数情况下,这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'));
Run Code Online (Sandbox Code Playgroud)
#red, #green { width: 50px; height: 50px; display: inline-block; }
#red { background-color: orangered; }
#green { background-color: yellowgreen; }
Run Code Online (Sandbox Code Playgroud)
<div id="game">
  <div id="red"></div>
  <div id="green"></div>
</div>
Run Code Online (Sandbox Code Playgroud)

现在让我们想象一下,有不同级别的红色和绿色块随机排列。在第 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'));
Run Code Online (Sandbox Code Playgroud)
#red, #green { max-width: 100px; padding: 10px; }
#red { background-color: orangered; }
#green { background-color: yellowgreen; }
Run Code Online (Sandbox Code Playgroud)
<div id="game">
  <div id="red">
    <div id="green"></div>
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)

正如您在点击绿色区域时所看到的,您同时输赢!如果您要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'));
Run Code Online (Sandbox Code Playgroud)
#red, #green { max-width: 100px; padding: 10px; }
#red { background-color: orangered; }
#green { background-color: yellowgreen; }
Run Code Online (Sandbox Code Playgroud)
<div id="game">
  <div id="red">
    <div id="green"></div>
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)

关键功能是filter。它将确保handler只有当点击实际上来自节点本身而不是来自其子节点之一时才会执行。

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

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');
});
Run Code Online (Sandbox Code Playgroud)
#red, #green { max-width: 100px; padding: 10px; }
#red { background-color: orangered; }
#green { background-color: yellowgreen; }
Run Code Online (Sandbox Code Playgroud)
<div id="game">
  <div id="red">
    <div id="green"></div>
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)

  • 当 JavaScript 在嵌套元素的同一事件上运行时,需要停止传播。想象一下在父元素和子元素上有一个单击事件。如果您单击了子级,并且不希望它也算作父级的单击,则需要在子级单击处理程序中停止传播。这并不常见,但有时是必要的。 (3认同)