Vue卡片內按按鈕也觸發外層:click stop阻止冒泡

按收藏按鈕時,卡片的開啟動作也一起執行。先看按鈕是否放在有click監聽的父元素裡。DOM的點擊事件通常會往父層傳播,內層按鈕的處理函式完成後,外層仍可能接到同一個事件。

如果希望這顆按鈕單獨執行,可以在Vue模板用 @click.stop停止事件傳播。這和取消連結的預設導覽不是同一件事;也和子元件emit的事件不同。先分清這次是DOM點擊往外傳,才能加在真正需要的地方。

先用計數看兩個處理函式

以下只示範事件範圍:外層增加卡片次數,內層增加按鈕次數。它不是完整的可存取卡片設計,也沒有真的開商品頁或保存收藏。

<script setup>
import { ref } from 'vue'
const cardHits = ref(0)
const buttonHits = ref(0)
</script>
<template>
  <div @click="cardHits++">
    <button @click="buttonHits++">收藏</button>
  </div>
  <p>{{ cardHits }} / {{ buttonHits }}</p>
</template>

點內層按鈕後,兩個計數都可能加一。因為這是同一個點擊先抵達按鈕,再沿DOM父層繼續傳播,不需要第二次真的點到外層。若卡片處理函式做導覽,使用者就會覺得自己只是按收藏,卻被帶走。

查修時從實際按鈕往上看DOM父層,不只看Vue元件名稱。元件模板形成的實際巢狀結構,才決定DOM事件會通過哪些元素。樣式看起來是並排,不代表DOM裡不是父子關係。

只在要獨立處理的按鈕stop

把內層監聽加上stop,Vue會替這個處理流程呼叫相應的事件傳播停止方法。外層普通冒泡click不再因為同一個點擊執行。

<div @click="cardHits++">
  <button @click.stop="buttonHits++">收藏</button>
</div>

這讓卡片其他地方仍可以執行原本的click,而收藏按鈕只做自己的事。不要在每個子元素都加stop,讓原本想由卡片統一處理的互動也全部被截斷。先畫出這次事件應走到哪一層,再選擇停止點。

如果需要在處理函式中判斷後才停止,也可以取得事件再呼叫 event.stopPropagation()。模板修飾符適合無條件停止的簡單情況;需要依資料條件決定時,函式內處理可能更清楚。兩種寫法應讓維護者看得出條件,不要重複堆疊卻不知道哪個生效。

真點擊比較內外次數

本文用Vue3.5.43與真Chromium,建立兩份相同的巢狀按鈕。一份沒有stop,一份有stop。點第一份按鈕後,內層1、外層1;點第二份後,內層1、外層0。

這份結果證明本例的普通冒泡click被停止,不包含真收藏API或真導覽成功。測自己的卡片時,可以先用相同計數替代業務動作,確認事件範圍,再接回保存與導覽。這樣能避免每次測都觸發正式工作。

修正後還要點卡片其他可操作位置,確認外層原功能仍存在。只測內層不觸發外層,可能漏掉你把整張卡片都改成不會執行的錯誤。內層單獨與外層按預期工作,是兩個都需要成立的結果。

stop不等於prevent

stopPropagation()停止事件繼續沿傳播路徑走,不會自動取消該元素的預設行為。preventDefault()則是取消可被取消的預設動作,例如某些連結導覽,卻不會因此阻止事件冒泡。MDN對這兩個API有明確區分。

在Vue裡,.prevent也不能當成.stop的替代。若你要同時阻止傳播與預設動作,可以按這個互動的實際需求組合修飾符;但不要因為看到兩個都叫阻止,就把所有click都寫成stop.prevent。

本文按鈕放在普通div內,主題是事件傳播,沒有連結預設導覽,所以只測stop。若真頁卡片是一個連結,而按鈕又嵌在裡面,除了修飾符還要核HTML結構是否合理。更清楚的設計往往是讓主要連結與次要按鈕各有自己的可操作區域,而不是靠大量阻止事件維持巢狀互動。

capture與self的用途也不同

父層若用捕獲階段監聽,可能在事件到達內層前已經執行。本文的外層是普通冒泡click,不把內層stop推成能回到過去取消所有已經發生的父層處理。若測試與預期不同,查是否有capture監聽或其他全域事件流程。

Vue的 .self要求事件來源就是目前元素,常用來讓背景本身的點擊觸發、忽略子元素來源。這與在特定按鈕上stop的範圍不同:self會對父層收到的來源做篩選,stop則讓那次事件不再繼續傳播。按你真正要保留的互動選,而不是把修飾符當作同義詞。

事件修飾符的排列也會影響生成的處理順序。需要組合時,按Vue官方事件指南理解每一個動作,再用小例子驗證。不要把別處的複合寫法整段貼過來,只因為它曾經讓某個按鈕不跳頁。

元件emit沒有DOM冒泡那條路

如果父元件收到的是子元件 emit('save'),這是Vue元件事件,不是DOM click沿父元素冒泡。元件事件本來就不會自動一路冒泡到所有祖先。這時要查父模板的事件綁定與子元件發出的名稱,不是到處加click.stop。

某個子元件可能同時包含原生按鈕與emit邏輯,兩層都存在,容易混淆。先確認父處理函式由哪一種事件觸發,再選合適修法。原生click停止了,不代表子元件沒有主動emit另一個業務事件。

如果發現一次按鈕操作保存兩次,也要查是否原生click與元件事件各呼叫同一個保存函式。stop只能處理DOM傳播,不能替你去除重複的業務綁定。計數例子能協助定位,但最後仍要把真正觸發鏈看清楚。

從最小事件範圍修起

先在內層加stop,核內外計數,再測卡片其餘入口。接回業務功能時,保存失敗、導覽與鍵盤操作仍依自己的UI需求驗證。不要為了防止一次冒泡,把原本的按鈕換成沒有語意的文字元素。

如果卡片本來就有多個獨立動作,最好讓DOM結構也清楚表達這些入口。事件修飾符能精確控制某次傳播,但不能取代整張卡片的操作設計。讓每個入口的目的與處理函式對上,比全面截斷所有事件更容易維護。

stopPropagation也不會取消同一個元素上其他已註冊的事件處理。若同一按鈕還被另一段程式綁定,按一次仍可能看到額外動作;這不是外層冒泡問題。先查相同元素的監聽來源,別一直往父層加stop。需要管理多個監聽時,讓註冊與解除位置明確,避免元件重建後重複綁定。本文的真案例每個按鈕只有一個內層處理函式,沒有推成所有外掛監聽都已排除。

參考資料

原文鏈接:https://wntheme.com/vue-click-stop-card-button/,轉載請註明出處。
0

評論0

顯示驗證碼
沒有帳號?註冊  忘記密碼?