1. 程式人生 > >一個因@click.stop引發的bug

一個因@click.stop引發的bug

問題

在專案頁面中使用 element popover,設定trigger='click'時點選外部不會觸發自動隱藏,但在 element 官網中是可以正常觸發的(官方示例),專案中的選單是自定義寫的,所以懷疑是有黑魔法。

查詢原因

  1. 將 popover 寫在app.vue根元件內,發現可以正常觸發自動隱藏。
  2. app.vue的 mounted 鉤子中加入window.addEventListener('click', () => console.log('window click===>>>>')),發現只有選單欄外層能夠觸發。
  3. 檢查選單欄元件,發現程式碼中<div class="main" @click.stop="isShowWhole = false">
    ,這裡的 click 事件使用了 stop 修飾符(阻止冒泡),可能阻止了 popover 外部點選的事件判斷,嘗試將 stop 修飾符去掉,發現外部點選事件正常觸發。

確認程式碼修改沒有副作用

在修復 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,並且深入分析問題的原因有助於能力的提高。