一個因@click.stop引發的bug
問題
在專案頁面中使用 element popover,設定trigger='click'
時點選外部不會觸發自動隱藏,但在 element 官網中是可以正常觸發的(官方示例),專案中的選單是自定義寫的,所以懷疑是有黑魔法。
查詢原因
- 將 popover 寫在
app.vue
根元件內,發現可以正常觸發自動隱藏。 - 在
app.vue
的 mounted 鉤子中加入window.addEventListener('click', () => console.log('window click===>>>>'))
,發現只有選單欄外層能夠觸發。 - 檢查選單欄元件,發現程式碼中
<div class="main" @click.stop="isShowWhole = false">
確認程式碼修改沒有副作用
在修復 bug 時,需要注意不會產生額外的 bug,那就需要了解修改的這段程式碼的含義
@click.stop="isShowWhole = false"
從程式碼上看,點選 class 為 main 的 div 將會觸發左邊側邊欄縮略顯示,加上 stop 修飾符是為了防止事件冒泡,所以能否去掉 stop 需要確認是否有這個必要。
// router.js
let routes = [
{
path : '/',
alias: '/admin',
component: Menu,
children: [...Pages],
},
{
path: '*',
name: '404',
component: NotFound,
},
];
複製程式碼
在路由中可以看到,Menu 是作為根路由進行渲染,除了 404 頁面都是它的子路由,所以 stop 修飾符是沒有必要加上的,去除後經過測試沒有其他影響。
深入 element popover 原始碼分析原因
對 element 元件進行 debug 時,可以直接引入相關元件的原始碼
import ElPopover from 'element-ui/packages/popover';
export default {
components: {
CheckboxFilter,
ElPopover
},
...
}
複製程式碼
然後我們就可以在node_modules
的 element 原始碼進行 debug 操作(危險步驟,debug 後需要復原)。
// node_modules/element-ui/packages/popover/src/main.vue
mounted() {
...
if (this.trigger === 'click') {
on(reference, 'click', this.doToggle);
on(document, 'click', this.handleDocumentClick);
} else if (this.trigger === 'hover') {
...
} else if (this.trigger === 'focus') {
...
}
}
複製程式碼
popover 在 mounted 鉤子內初始化了trigger='click'
的事件繫結,on(document, 'click', this.handleDocumentClick)
這裡綁定了 document 很可能就是阻止事件冒泡後不能觸發外部點選隱藏的判斷邏輯。
// node_modules/element-ui/packages/popover/src/main.vue
handleDocumentClick(e) {
let reference = this.reference || this.$refs.reference;
const popper = this.popper || this.$refs.popper;
if (!reference && this.$slots.reference && this.$slots.reference[0]) {
reference = this.referenceElm = this.$slots.reference[0].elm;
}
if (!this.$el ||
!reference ||
this.$el.contains(e.target) ||
reference.contains(e.target) ||
!popper ||
popper.contains(e.target)) return;
this.showPopper = false;
},
複製程式碼
這裡判斷this.$el
是否包含 click 的 target,從而是否觸發this.showPopper = false
,當選單欄阻止事件冒泡後 document 不能監聽到 click 事件,才會無法進行外部點選隱藏的判斷邏輯。
延伸v-clickoutside
element 的 select 元件中用到了 v-clickoutside 自定義指令,作用和 popover 的 handleDocumentClick 差不多(倒不如說 handleDocumentClick 是特殊的 clickoutside)
在上面的問題中,我們單獨把 v-clickoutside 抽出來使用確實可以的,這是為什麼呢?
// node_modules/element-ui/packages/popover/src/utils/clickoutside.js
!Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e));
!Vue.prototype.$isServer && on(document, 'mouseup', e => {
nodeList.forEach(node => node[ctx].documentHandler(e, startClick));
});
複製程式碼
答案是 v-clickoutside 使用滑鼠事件判斷的,所以 click 的 阻止冒泡不會讓 clickoutside 無效。
總結
解決 bug 的過程中需要做到不產生額外的 bug,並且深入分析問題的原因有助於能力的提高。