紀念逝去的頭髮--一次debug經歷
我們公司有一個pc端的客戶端,內嵌了一個谷歌的瀏覽器。 為了處理某些奇奇怪怪的需求、會按照某些規則遮蔽鍵盤事件。只有在當前focus可編輯的時候, 才可以使用backspace鍵這樣子, 客戶端研發那邊使用谷歌的FocusOnEditableField 的API來識別當前的編輯狀態

我在使用element重構專案之後,使用了el-select的遠端查詢、篩選功能,在這裡 發現el-select元件全部無法觸發FocusOnEditableField了。。。於是開始了dubug之旅。
這個api提到 當前focus的元素如果是可編輯狀態,就返回true。這讓我想起了dom裡面很冷門的一個api
contenteditable 屬性規定是否可編輯元素的內容。 複製程式碼
OK, 來試一下。 這裡有幾個api可以幫我測試一下
document.activeElement獲取當前文件中獲得焦點的元素 dom.contentEditable獲取當前元素的contentEditable值 複製程式碼
mounted () { document.onkeydown = function (e) { console.log(document.activeElement.contentEditable) } }, 複製程式碼
但是實際打印出來的都是inherit, 看來contentEditable方法也和 dom.style
一樣只能獲得行內的值。 翻翻MDN 找到了這個api isContentEditable
他可以獲取計算之後的contentEditable的值。
OK。再來試試
mounted () { document.onkeydown = function (e) { console.log(document.activeElement.isContentEditable) } }, 複製程式碼
在select組建中測試返回false。OK。 我們直接通過修改dom屬性的方式來看看是不是這個屬性的問題。
mounted() { if (this.$refs.elSelect.$children[0].$options.name === 'ElInput') { this.$refs.elSelect.$children[0].$refs.input.contentEditable = true } }, 複製程式碼
元件加載出來之後,暴力的修改contentEditable屬性。。 測試一下, 功能OK, 打完收工~。
當然不可能, 雖然實現了功能,但是有很多疑點啊。 原生的input和elemen庫的input都可以正常使用, 應該還有更深層次的問題。。 沒辦法了, 看原始碼吧
從package裡得知,element通過USE引入的元件都是從 \node_modules\element-ui\lib\element-ui.common.js
這裡引入的。 package裡的是未編譯過的元件。 開始翻閱原始碼
然後一無所獲。。

編譯過的這個檔案雖然可以打斷點, 但是沒有辦法去看具體dom的繫結屬性。
那就用更簡單暴力的方式, 直接從頁面上匯入element未編譯的組建。 node_modules/element-ui/packages/select/src/select.vue
就是他了。 然後專案就掛了。這個元件還引入了其他元件, 其中有一部分使用了jsx。 再該環境太麻煩了。直接把和bug無關的程式碼全部幹掉, 專案總算又跑起來了。

這樣就清晰多了。。
來看看element的select組建, 漲姿勢了。 compositionstart、 compositionupdate、compositionend
這三個事件, 新姿勢get~。
迴歸業務,繼續找bug。
嗯~~~ disabled、readonly
這兩個屬性比較可疑啊。
刪掉readonly果然就OK了。 看來問題出現在這裡了。
再看看相關的程式碼
// 點選父元素的時候 toggleMenu() { if (!this.selectDisabled) { if (this.menuVisibleOnFocus) { this.menuVisibleOnFocus = false; } else { this.visible = !this.visible; } if (this.visible) { (this.$refs.input || this.$refs.reference).focus(); } } }, 複製程式碼
computed: { readonly() { // trade-off for IE input readonly problem: https://github.com/ElemeFE/element/issues/10403 const isIE = !this.$isServer && !isNaN(Number(document.documentMode)); return !this.filterable || this.multiple || !isIE && !this.visible; }, } 複製程式碼
邏輯就應該是, 點選父元素的時候, 更改visible的值, 然後通過計算屬性計算出readonly, 然後通過v-bind來修改dom的屬性。
看來找到原因了,看起來這個操作是同步事件, 實際上dom的更新是非同步事件。
而谷歌的FocusOnEditableField邏輯應該是, 有元素進入focus->檢查該元素的readonly、contentEditable等屬性。
而實際上呢。修改了 readonly -> 呼叫focus事件 這裡並不是同步的, input在只讀狀態進入了focus。 所以無法觸發FocusOnEditableField事件了。
具體的可以參考Vue.nextTick方法
寫個demoe來試試
<div style="width: 300px;height: 300px;background-color: pink" @click.stop="toggleMenu"> <input ref="input" type="text" :readonly="readonly"> </div> 複製程式碼
data () { return { readonly: true } }, methods: { toggleMenu() { this.readonly = false console.log(this.$refs.input.readOnly ) }, } 複製程式碼
嗯~果然。第一次點選的時候列印的是true。在改一下
toggleMenu() { this.readonly = false this.$nextTick(() => { console.log(this.$refs.input.readOnly) this.$refs.input.focus() }) }, 複製程式碼
總算找到了問題的原因。
望著桌子上的頭髮, 這波不虧。
ps: 最近在翻閱element的原始碼, 收穫頗豐。element封裝的dispatch方法、broadcast方法也給我以後處理vue組建提供了靈感。感謝大佬們的無私奉獻。