一個開源組件 bug 引起的分析

這是一個悲傷的故事。某日清晨,距離版本轉測還剩一天,切圖仔的我正按照計劃有條不紊的畫頁面。當我點擊一個下拉彈框組件中分頁組件頁數過多而出現的向後 5 頁省略號時,悲劇開始了,彈框被收回了。情景再現
filejavascript

問題

問題的表象很簡單,使用的是組件庫的下拉彈窗組件,在組件中使用到了分頁組件,當點擊分頁組件的向後 5 頁快速跳轉時,彈窗被收回了。咱們的預期是可以繼續操做的,只有點擊彈框外部時,彈窗纔會被收回。vue

分析

發現這個問題我作了以下分析:java

  1. 肯定這是一個問題
    再次重複操做問題,肯定問題出現的條件,可以在特定條件下復現的問題纔是問題。我穩定的復現了問題條件是:分頁組件出現向前跳轉 5 頁或向後跳轉 5 頁,點擊到不會再出現向前跳轉 5 頁或向後跳轉 5 頁這樣的快速跳轉後,彈框會被收回。git

  2. 肯定是本身組件配置使用問題仍是組件自己 bug
    我在組件的文檔中並無看到有關這個問題的特別說明。接着我又在官方提供的代碼運行環境中,寫了一個使用的 demo(實際項目中的代碼複雜度較高,可能存在被其餘樣式等因素影響而產生問題,使用功能單一的 demo 有助於咱們快速肯定問題範圍,且官方運行環境通常使用的是其最新的組件版本),發現與我在項目中使用存在一樣問題。到這裏能夠排除是組件配置使用,組件庫版本不是最新,及項目環境影響致使的問題。github

  3. 在組件庫的開源項目中查詢 issue
    在 issue 中搜索關鍵字查詢是否有相關 issue,若是運氣好找到相關問題,通常會有相關的討論,就算沒有具體的解決措施也能讓你明白問題大概在哪裏。我運氣比較差,沒有找到相關問題的 issue,因此只能本身提一個 issue 了。有時間有耐心等待維護人解決你提的 issue,問題分析到這裏就能夠結束了。npm

  4. 查看組件源碼
    顯然個人時間很緊急,問題必須儘快解決,且我也想肯定一下問題具體在哪裏,看本身可否解決。很快我找到的相關組件的源碼,通過定位發現,彈框下拉組件中使用了 v-click-outside 指令,用來觸發點擊指令以外的元素收起彈框。
    進一步深刻源碼,發現 v-click-outside 組件的原理,是在 document 組件上註冊了一個 click 監聽事件,當有點擊事件發生,監聽函數會判斷,點擊事件發生的元素,是否包含在使用指令的元素中。若是是監聽函數返回,不然觸發指令綁定的事件(在彈框組件中就是隱藏彈框)。代碼片斷以下
// el 表示指令綁定的元素,event爲點擊事件
    const isClickOutside = event.target !== el && !el.contains(event.target)

    if (!isClickOutside) {
            return
    }

    if (middleware(event, el)) {
            handler(event, el)
    }
  1. 斷點調試
    通過上面的步驟事後,我尚未發現問題的所在,因此只能打斷點進行代碼調試了。通過調試發現問題出在 el.contains(event.target) 這一段,分頁組件中的向前 5 頁元素,點擊觸發後就被 v-if 指令從頁面中移除了。因此運行到這一句時,el 中是不包含向前 5 頁元素的。也就是說這裏向前 5 頁元素被錯誤的判斷爲不在 el 元素中,指令綁定的彈框收起函數被觸發,彈框被收回了。

解決問題

問題已經找出,如今問題是怎麼解決,由於問題是出在組件中,可是咱們又不能直接修改組件庫代碼,直接修改項目依賴代碼,不利於代碼維護,也治標不治本。最好是能在項目代碼中添加代碼解決這個問題。ide

開始時走了一些彎路,想着用僞元素去覆蓋,被點擊的元素,試圖去混淆 e.target,讓 el.contains(event.target) 爲true。這樣確實取得了一些成效,可是在頁數更多了之後依然有問題,並且我也不是很清楚這樣用僞元素遮擋可讓事件觸發元素不是原本元素的原理是什麼。更重要的一點是,寫這篇文章的時候我徹底回憶不出當時我爲何會想到這個方案,多是靈感吧,解決問題真的很須要靈感。函數

在使用了彎路解決辦法暫時解決問題後,我開始思考更完全的解決方案。歸根結底問題是出在了 v-click-outside 指令上,若是我夠找到一種正確的判斷方式,讓被刪除的元素也能夠被判斷爲在指令註冊元素中,那麼問題將獲得完全解決。通過不斷的嘗試與思考,我發現將點擊事件註冊在捕獲階段觸發時,獲得的指令綁定元素中依然有應該被刪除的點擊元素。到這裏問題基本就明朗了,完全的解決方案也就出現了。調試

因爲組件引入的 v-click-outside 指令是局部註冊在下拉彈框組件上的,因此我使用了 vue 的extends 繼承了組件的彈框下拉組件。在繼承的組件中從新註冊了指令 v-click-outside,該指令註冊在 document 上的點擊事件是捕獲階段觸發的。code

因爲是繼承覆蓋組件庫中的組件,因此組件庫升級不會帶來太大的影響,同時也不用從新寫一個組件,減小了工做量。到這裏問題就獲得了完全的解決。

輸出 v-click-out 包

解決完問題以後,我在 npm 上搜索了一些 click-outside 相關的包,發現這些包中註冊在 document 上的點擊事件,廣泛是在冒泡階段觸發的,也就是說都存在文中我所遇到的問題。因而通過幾天業餘時間的努力,我開源了一個基於 vue 的 click-outside 指令開源項目catch-click-outside(不要臉求star)

一點思考

在以前的工做中,我也解決了很多問題,此次之因此會記錄解決過程,一個是由於這個問題我產生了一些輸出,可能對別人會有些微的幫助。另外一個比較重要的緣由是,在解決問題過程當中走了一些彎路,也遇到了一些坎,可是這些問題都在不停的思考與事件中逐漸清晰,而後被解決,我以爲這個過程很值得記錄。文字功底有限,這個解決過程記錄的平淡無奇,謹以此做爲備忘。

轉載請註明出處!

相關文章
相關標籤/搜索