一個小需求
事情的起因,是昨天有一個新的需求被提出。
需求是要實現,讓我們自己定製的彈出層,具備按下 ESC 也能退出的功能。我把任務交給了同組的小夥伴S去實現。(這個專案用到了vue技術棧,以及餓了麼的UI框架。)
我開完會回來,發現他還在處理那個功能,但好像遇到了什麼瓶頸。於是,我就問他,卡在了什麼地方。
小夥伴S說,他百度了不少資料,還查了官方文件,並且嘗試其中的辦法,但就是無法觸發按下 ESC 的回撥方法,很是鬱悶。我看了他的程式碼,他的寫法是這樣的:
<div class="custom-modal" @keydown.27="handlePressEscape">
...
</div>
複製程式碼
...
handlePressEscape () {
console.log('press escape!');
},
...
複製程式碼
他的想法不錯,因為是自定義的彈出層,所以就想著把 keydown 事件,繫結在最外層的 div 上,讓整個彈出層都能監聽到。
他給我看了他查的資料,幾乎都是在 input 上繫結 keydown 事件的例子,而 vue 的官方文件裡也是類似的例子,實踐後卻陷入了瓶頸。但是他忽略了一個問題,keydown 事件,並非綁在任意一個標籤上,都會起作用。
一種思路
我沒有直接把答案告訴他,而是給他提供了一個思路:在我們常用的 element-ui 的 el-dialog 元件裡,有個屬性叫做 close-on-press-escape
,它的解釋如下圖:
從文件的解釋,可以看出 el-dialog 在預設情況下,已經實現了我們需要解決的需求。所以,我讓他看看 el-dialog 的原始碼,是如何實現的。
他一聽要看原始碼,就露出了恐懼之情。
原始碼是所有框架和API的根基,因為比較複雜深邃,所以讓人很抗拒。我自己也經歷過這個階段,所以非常理解他的心情,鼓勵他一起做一次嘗試。
查詢原始碼
首先,我們在 node_modules 裡,找到了 element-ui的資料夾,它大致長這個樣子:
接著,我們找到了 packages 裡的 dialog 資料夾,再從 index 入口,找到了元件 component.vue。可是,點進去找了半天,也只找到個 closeOnPressEscape
屬性的定義,卻沒有實現的方法。
...
closeOnPressEscape: {
type: Boolean,
default: true
},
...
複製程式碼
這麼神奇麼?只定義一個屬性,就能實現一個事件的互動了?
感覺不太可能啊?!? 為了揭開迷霧,繼續找。。。
仔細瀏覽了 component.vue 檔案,發現在 script 裡,引入下面 3 個檔案:
import Popup from 'element-ui/src/utils/popup';
import Migrating from 'element-ui/src/mixins/migrating';
import emitter from 'element-ui/src/mixins/emitter';
...
複製程式碼
在第一個引入的 Popup 中,竟然也發現了 closeOnPressEscape
,感覺似乎找對方向了。
但令人沮喪的是,Popup 中同樣只有 closeOnPressEscape
的屬性定義,卻沒有實現。不過,它卻引入了另一個輔助檔案 PopupManager
,再點進去找。
哇!終於找到了!它的實現,是這樣的:
// handle `esc` key when the popup is shown
window.addEventListener('keydown', function(event) {
if (event.keyCode === 27) {
const topPopup = getTopPopup();
if (topPopup && topPopup.closeOnPressEscape) {
topPopup.handleClose
? topPopup.handleClose()
: (topPopup.handleAction
? topPopup.handleAction('cancel')
: topPopup.close());
}
}
});
複製程式碼
原來,是在 window 上新增了事件監聽 keydown,當監測到是 ESC 的 keyCode 時,則執行相關操作。
模仿原始碼
ok,現在已經知曉了原理,那就按照我們的實際需求,模仿改造一下:
...
props: {
...
closeOnPressEscape: {
type: Boolean,
default: true
}
},
...
mounted () {
window.addEventListener('keydown', this.handlePressEscape);
},
destroyed () {
window.removeEventListener('keydown', this.handlePressEscape);
},
methods: {
...
handlePressEscape (event) {
if (this.closeOnPressEscape && event.keyCode === 27) {
this.handleClose();
}
}
}
複製程式碼
在上述實現中,有2個需要注意的點:
-
程式碼方面,在 mounted 中,給 window 新增事件監聽之後,要記得在 destroyed 時,去除監聽。
-
業務方面,這是一個我們定製的通用的彈出層元件,所以在 props 中定義了一個 closeOnPressEscape 屬性,以方便在某些業務場景下,不需要按 ESC 就退出這個功能的時候,直接設定它為 false 即可。
到此為止,整個事件畫上了圓滿的句號。
原始碼真有那麼可怕嗎?
原始碼一詞,乍一聽就是神祕、高大上、吊炸天的代名詞,讓很多的前端同學聞風喪膽。回想當初,我也曾一度對它有一股深深的恐懼。
原始碼真的這麼可怕嗎?
從以上的事例中可以看出,其實並沒有。例子中的element-ui原始碼並不複雜,我和小夥伴S一起看原始碼時,他也大概都能看得明白。最後因為弄懂了背後的原理,進行簡單應用,比較輕鬆地就解決了問題。
對於原始碼的恐懼,讓我們漸漸思維固化,自己告訴自己不要去碰原始碼,時間長了就遺忘了還有這樣一條路可走。
面試中的應用
關於對原始碼的考察,我也會經常應用在面試中。在面試中,如果候選人給我的感覺不錯,我的慣用伎倆是問下面這個問題:
剛才你說到,用過一段時間 xxx 框架,xx API屬性也用過,也很清楚它達到的效果。
那麼現在,如果需要你實現一個類似的效果,拋開 xxx 框架以及 xx API屬性,
你會如何去實現,有沒有其他思路?
這個問題,意在考量候選人的思維方式和解決問題的能力,以及把他思考的過程闡述清楚的表達能力。這三種能力,往往比使用過某些框架的經驗,更讓我看中。
這道題的回答思路,其實就是可以通過挖掘原始碼,去實現功能。另外也可以通過海量地查詢資料,發現原生js的實現方式,但這條路沒有直接挖掘原始碼來得快。在遇到實際的業務問題的時候,參考原始碼的原理和寫法,往往能更快地解決問題。
這是我自己對這道題目,給出的答案。
一點點思考
昨天的案例,引發了我的一連串思考:
現代框架的確降低了前端入門的門檻,提高了開發效率。
但是,在使用這些框架的過程中,我們到底學到了什麼?
脫離了框架和它的API,我們腦海中還剩下些什麼?
以至於,當下一個更新更棒的框架出現的時候,我們是否能夠用已經學到的知識,幫助自己更迅速地上手?
知其然,並知其所以然,學習所有的知識都應當有這種探索精神。我們不僅僅是程式碼的搬運工。領悟這些深層次的原理,比起僅僅熟練地掌握一門框架,要實用得多。
PS:歡迎關注我的公眾號 “超哥前端小棧”,交流更多的想法與技術。