1. 程式人生 > >一個開源元件 bug 引發的分析

一個開源元件 bug 引發的分析

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

問題

問題的表象很簡單,使用的是元件庫的下拉彈窗元件,在元件中使用到了分頁元件,當點選分頁元件的向後 5 頁快速跳轉時,彈窗被收回了。我們的預期是能夠繼續操作的,只有點選彈框外部時,彈窗才會被收回。

分析

發現這個問題我做了如下分析:

  1. 確定這是一個問題
    再次重複操作問題,確定問題出現的條件,能夠在特定條件下復現的問題才是問題。我穩定的復現了問題條件是:分頁元件出現向前跳轉 5 頁或向後跳轉 5 頁,點選到不會再出現向前跳轉 5 頁或向後跳轉 5 頁這樣的快速跳轉後,彈框會被收回。

  2. 確定是自己元件配置使用問題還是元件本身 bug
    我在元件的文件中並沒有看到有關這個問題的特別說明。接著我又在官方提供的程式碼執行環境中,寫了一個使用的 demo(實際專案中的程式碼複雜度較高,可能存在被其他樣式等因素影響而產生問題,使用功能單一的 demo 有助於我們快速確定問題範圍,且官方執行環境一般使用的是其最新的元件版本),發現與我在專案中使用存在同樣問題。到這裡可以排除是元件配置使用,元件庫版本不是最新,及專案環境影響導致的問題。

  3. 在元件庫的開源專案中查詢 issue
    在 issue 中搜索關鍵字查詢是否有相關 issue,如果運氣好找到相關問題,一般會有相關的討論,就算沒有具體的解決措施也能讓你明白問題大概在哪裡。我運氣比較差,沒有找到相關問題的 issue,所以只能自己提一個 issue 了。有時間有耐心等待維護人解決你提的 issue,問題分析到這裡就可以結束了。

  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 元素中,指令繫結的彈框收起函式被觸發,彈框被收回了。

解決問題

問題已經找出,現在問題是怎麼解決,因為問題是出在元件中,但是我們又不能直接修改元件庫程式碼,直接修改專案依賴程式碼,不利於程式碼維護,也治標不治本。最好是能在專案程式碼中新增程式碼解決這個問題。

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

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

由於元件引入的 v-click-outside 指令是區域性註冊在下拉彈框元件上的,所以我使用了 vue 的extends 繼承了元件的彈框下拉元件。在繼承的元件中重新註冊了指令 v-click-outside,該指令註冊在 document 上的點選事件是捕獲階段觸發的。

由於是繼承覆蓋元件庫中的元件,所以元件庫升級不會帶來太大的影響,同時也不用重新寫一個元件,減少了工作量。到這裡問題就得到了徹底的解決。

輸出 v-click-out 包

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

一點思考

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

轉載請註明出處