大家好,我是若川。最近組織了源碼共讀活動,感興趣的可以加我微信?ruochuan12?參與,已進行了三個多月,大家一起交流學習,共同進步。
前言
vue簡潔好用體現在很多個地方,比如其內置了32+修飾符,可以很方便我們阻止冒泡、阻止默認事件、鼠標事件處理、系統鍵盤事件等等,讓我們可以快速搞定業務,簡直不要太方便噢!!!
耽誤您15分鐘您可以收獲:
32+修飾符(包括事件修飾符、鼠標修飾符、表單修飾符、系統修飾符等等)的含義和使用
如何利用webpack動態注冊vue路由,再也不手寫路由配置啦!
文章中例子都放在了github源碼上,也可以點擊直接看例子
如何動態注冊路由?
文中的每個修飾符例子都由一個頁面承載,聰明的你肯定不想手動引入幾十個.vue文件并配置路由.
有什么辦法可以幫我們自動完成路由注冊呢?
1. 文件目錄結構
目錄結構(已去除其他文件目錄)大概如下
├──?package.json
└──?src├──?App.vue├──?main.js├──?router.js└──?views├──?About.vue├──?Home.vue└──?modifiers├──?capture.vue├──?once.vue├──?order.vue├──?passive.vue├──?prevent.vue├──?self.vue└──?stop.vue└──?...
2. 期望的路由配置
最終給到
vue-router
的配置大概長下面這個樣子,每個配置最重要的部分分別是path
、name
和component
[{"path":?"/home","name":?"home","component":?{"name":?"Home","methods":?{},"staticRenderFns":?[],"_compiled":?true,"_scopeId":?"data-v-fae5bece","beforeCreate":?[null],"beforeDestroy":?[null],"__file":?"src/views/Home.vue"}},{"path":?"/modifiers/capture","name":?"modifiersCapture","component":?{"name":?"capture","methods":?{},"staticRenderFns":?[],"_compiled":?true,"_scopeId":?"data-v-63b4eeee","beforeCreate":?[null],"beforeDestroy":?[null],"__file":?"src/views/modifiers/capture.vue"}},...?//?其他路由配置
]
3. require.context實現動態注冊路由
借助webpack require.context 的能力,可以非常方便地實現上面目錄到路由配置的映射工作,源碼如下
const?registerRoutes?=?()?=>?{const?contextInfo?=?require.context('./views',?true,?/.vue$/)const?routes?=?contextInfo.keys().map((filePath)?=>?{//?filePath?形如?./Home.vue、./modifiers/capture.vue//?path我們希望是/home、/modifiers/capture//?所以需要把開頭的./和.vue都替換為空const?path?=?filePath.toLowerCase().replace(/^\.|\.vue/g,?'')//?name的話將/home、/modifiers/capture轉成小駝峰即可//?把開頭的/先替換掉,再把第一個/后的單詞變成大寫就可以了const?name?=?path.replace(/^\//,?'').replace(/\/(\w)/,?($0,?$1)?=>?$1.toUpperCase())//?通過require去讀取.vue文件內容const?component?=?require(`./views${filePath.replace(/^\./,?'')}`).defaultreturn?{path,name,component}})return?routes
}
效果
經過上面的簡單處理,動態注冊路由就完成啦!您也可以點擊vue-demos查看效果
事件修飾符
1. 阻止冒泡的兩種方式
<template><div?class="parent"?@click="onClickParent">我是爸爸<div?class="child"?@click="onClickChild">我是兒子</div></div>?
</template>export?default?{name:?'stop',methods:?{onClickParent?()?{console.log('我是爸爸')},onClickChild?()?{console.log('我是兒子')}}
}
點擊子節點的時候因為事件冒泡的緣故不僅會打印出我是兒子
還會打印我是爸爸
。有什么辦法可以阻止子節點的事件冒泡呢?

1 .stop
只要加.stop修飾符即可,阻止事件冒泡的及簡方式,很方便是不是。
當添加上.stop
修飾符時,只會出現我是兒子
<template><div?class="parent"?@click="onClickParent">我是爸爸<div?class="child"?@click.stop="onClickChild">我是兒子</div></div>?
</template>

2. event.stopPropagation
當然了,我們也可以通過調用
event.stopPropagation
來阻止冒泡。不過更加推薦修飾符的做法,這樣你的函數會更加專注在邏輯處理上,而不用關心DOM事件細節
export?default?{name:?'stop',methods:?{onClickChild?(event)?{console.log('我是兒子')event.stopPropagation()}}
}

2. 阻止默認事件的兩種方式
vue中阻止冒泡有兩種方式,那阻止默認事件呢?
1 .prevent
<template><div?class="prevent"><a?href="https://juejin.cn/"?@click="onNoPrevent">點擊跳轉掘金</a><br?/><br?/><a?href="https://juejin.cn/"?@click.prevent="onPrevent">阻止默認事件,無法跳轉掘金</a></div>
</template>export?default?{name:?'prevent',methods:?{onNoPrevent?()?{console.log('未阻止默認事件')},onPrevent?()?{console.log('阻止默認事件')}}
}
只要添加.prevent
輕松實現阻止默認事件

2.event.preventDefault()
和阻止冒泡一樣,我們也可以通過調用事件對象的
preventDefault
方法來阻止默認事件
export?default?{name:?'prevent',methods:?{onPrevent?(event)?{console.log('阻止默認事件')event.preventDefault()}}
}
3 .capture
默認情況下,事件流是以
冒泡
(由里向外)的形式傳遞的,如果想以捕獲(由外向里)
的形式應該怎么辦呢?
<template><div?class="capture?parent"?@click.capture="onClickParent">父節點<div?class="child"?@click.capture="onClickChild">自節點</div></div>
</template>export?default?{name:?'capture',methods:?{onClickParent?()?{console.log('我是父節點')},onClickChild?()?{console.log('我是子節點')}}
}
不加catpture
修飾符,點擊子節點會陸續打印我是父節點以及我是子節點,加了之后,則是反過來了

4 .self
只有當
event.target
是當前元素自身時才會觸發事件回調函數
<template><div?class="self"?@click.self="onClickSelf"><div?class="inner"?@click="onClickInner"></div></div>
</template>export?default?{name:?'self',methods:?{onClickSelf?()?{console.log('我是self節點')},onClickInner?()?{console.log('我是inner節點')}}
}
不加self
修飾符的話,點擊inner
節點也會觸發self
的事件,加了之后只有觸發事件的元素本身是self
,才會打印出我是self節點

暫停一下:修飾符的順序如何理解?
已經回顧了4個修飾符,單獨使用的時候很容易理解,但是注意官網有這么一句話

怎么理解呢?我們來看兩個栗子
<template><div?class="order"><div?class="order-0"><a?href="https://juejin.cn/"?class="order-parent"?@click.self.prevent="onClickParent">我是父節點,會跳轉掘金<br?/><span?class="order-child"?@click="onClickChild">我是子節點</span></a><hr?/></div><div?class="order-2"><a?href="https://juejin.cn/"?class="order-parent"?@click.prevent.self="onClickParent">我是父節點,無法跳轉掘金<br?/><span?class="order-child"?@click="onClickChild">我是子節點</span></a></div></div>?
</template>export?default?{name:?'order',methods:?{onClickParent?()?{console.log('我是父節點')},onClickChild?()?{console.log('我是子節點')}}
}
您可以猜一下,上面的代碼會發生什么,以下三點是可以明確的?
首先可以明確的是點擊上面和下面的子節點都不會觸發父節點的點擊事件
點擊下面的父節點會打印出我是父節點,但是不會跳轉掘金
點擊上面的父節點會打印出我是父節點,也不會跳轉掘金
但是點擊上面的子節點,父節點會不會跳轉至掘金呢?答案是會
為什么?
a@click.self.prevent="onClickParent"
的意思是當點擊的元素是a元素本身時,會阻止默認事件(可以解釋3,不會發生跳轉),并且執行onClickParent
回調。
而點擊span元素時,由于冒泡的緣故,點擊事件會傳遞給a,但是此時a會判斷出該事件不是由自身觸發的也就不會阻止默認事件
(此時也就發生跳轉了),當然也不會觸發onClickParent
回調
同理來我們分析一下a@click.prevent.self="onClickParent"
不管是子節點還是自身點擊,都是先阻止默認事件,只有當觸發點擊事件是a元素本身時才會執行onClickParent
回調函數。

回過頭看,你理解事件的順序含義了嗎?

5. once
顧名思義,事件只會觸發一次
<template><div?class="once"?@click.once="onClickOnce">只觸發一次</div>
</template>export?default?{name:?'once',methods:?{onClickOnce?()?{console.log('once,我只會觸發一次點擊事件回調')}}
}
觸發一次點擊之后,任我再怎么點,回調怎也不會觸發了。

6 .native
我們知道在自定義組件上,只能監聽自定義事件,一些原生事件(比如click)是沒有辦法直接觸發的,但是使用
.native
修飾符可以幫我們辦到這點
native.vue
<template><div?class="native-custom"><input?type="text"?@keydown="onKeydown"></div>
</template>export?default?{name:?'nativeCustom',methods:?{onKeydown?()?{this.$emit('onKeydown')}}
}
custom.vue
<template><div?class="native"><!--?加上.native之后原生事件才得以監聽成功?--><NativeCustom?@onKeydown="onKeydown"?@click.native="onClick"?/></div>
</template>import?NativeCustom?from?'../../components/native.vue'export?default?{name:?'native',components:?{NativeCustom},methods:?{onKeydown?()?{console.log('onKeydown')},onClick?()?{console.log('onClick')}}
}

7 .passive
vue對應?
addEventListener
?中的?passive
?選項提供了?.passive
?修飾符
<!--?滾動事件的默認行為?(即滾動行為)?將會立即觸發?-->?
<!--?而不會等待?`onScroll`?完成?-->?
<!--?這其中包含?`event.preventDefault()`?的情況?-->?<div?v-on:scroll.passive="onScroll">...</div>
這個修飾符對于滾動性能的提升,一直沒找到合適的例子,跪求廣大掘友有例子啊
這個修飾符對于滾動性能的提升,一直沒找到合適的例子,跪求廣大掘友有例子啊
這個修飾符對于滾動性能的提升,一直沒找到合適的例子,跪求廣大掘友有例子啊
v-bind修飾符
8 .sync
當我們想要在
父組件
和子組件
之間對某個屬性值進行雙向綁定時,有什么便捷的方式?是的只要.sync
修飾符即可辦到
父組件
<template><div?class="sync-parent">我是父組件:?{{?text?}}<Child?:text.sync="text"?/></div>
</template>import?Child?from?'./child.vue'export?default?{name:?'SyncParent',data?()?{return?{text:?'parent'}},components:?{Child,}
}
子組件
<template><div?class="child">我是子組件:?<input?type="text"?v-model="value"?@input="onInput"></div>
</template>export?default?{name:?'child',props:?{text:?{type:?String}},data?()?{return?{value:?this.text}},methods:?{onInput?()?{//?注意這里,必須是update:xxx的形式xxx即屬性propthis.$emit('update:text',?this.value)}}
}

9 .camel
.camel
?修飾符允許在使用 DOM 模板時將?v-bind
?property 名稱駝峰化,例如 SVG 的?viewBox
?property:
<svg?:view-box.camel="viewBox"></svg>
10 .prop
關于.prop修飾符官網只有這句話
.prop
? 作為一個 DOM property 綁定而不是作為 attribute 綁定。`。
有啥作用?
通過自定義屬性存儲變量,避免暴露數據
防止污染 HTML 結構
比如有以下代碼
<template><div?class="prop"><div?class="prop-item"?:my-name="prop"></div>//?最終變成了?<div?my-name="hello?prop"?class="prop-item"></div><div?class="prop-item"?:my-name.prop="prop2"></div>//?最終變成了<div?class="prop-item"></div><button?@click="onGetResult">獲取結果</button></div>
</template>export?default?{name:?'prop',data?()?{return?{prop:?'hello?prop',prop2:?'hello?prop2'}},methods:?{onGetResult?()?{const?$refProp?=?this.$refs.propconst?$refProp2?=?this.$refs.prop2console.log($refProp.getAttribute('my-name'))?//?hello?propconsole.log($refProp2.getAttribute('my-name'))?//?null}}
}
從示例上可以看出未使用.prop
修飾符的my-name
屬性會綁定到dom節點的attribute,從而出現暴露的情況。

鼠標修飾符
當咱們想監聽用戶點擊了
左鍵
、右鍵
或者中鍵
時也有修飾符可以快捷使用,分別是.left
、.right
、middle
,來看個例子試試
根據MDN MouseEvent.button,介紹。

在最外層div.mouse
監聽mousedown
事件,看下用戶點擊的是鼠標哪個鍵,三個button
分別用三個修飾符快捷方式監聽左鍵
、中鍵
、右鍵
并打印出left
、middle
、right
<template><div?class="mouse"?@mousedown="onMousedown"><button?@click.left="onClickBtn('left')">left</button><button?@click.middle="onClickBtn('middle')">middle</button><button?@click.right="onClickBtn('right')">right</button></div>
</template>export?default?{name:?'mouse',mounted?()?{},methods:?{onClickBtn?(msg)?{console.log(msg)},onMousedown?(event)?{const?mosueMsgMap?=?{0:?'鼠標左鍵',1:?'鼠標中鍵',2:?'鼠標右鍵'}console.log('點擊了',?mosueMsgMap[event.button])}}
}
沒有帶鼠標回來,中鍵點擊暫時不能演示,后續會補上

11 .left
同上例子,監聽鼠標左鍵點擊
12 .right
同上例子,監聽鼠標右鍵點擊
13 .middle
同上例子,監聽鼠標中鍵點擊
表單相關修飾符
14 .trim
對于輸入的內容,希望可以
過濾首尾空格
應該怎么做呢?
<template><div?class="trim"><div?class="trim-item"><input?type="text"?v-model="name"><p>用戶名:<span>{{?name?}}</span></p></div><div?class="trim-item"><input?type="text"?v-model.trim="name2"><p>用戶名2:<span>{{?name2?}}</span></p></div></div>
</template>export?default?{name:?'trim',data?()?{return?{name:?'',name2:?'',}},watch:?{name?(newVal)?{console.log(`'----${newVal}----'`)},name2?(newVal)?{console.log(`'----${newVal}----'`)},}
}
.trim修飾符可以很方便做到

15 .lazy
v-model
大家都很熟悉,默認情況下,每次input事件
觸發的時候都會將輸入框的值與其綁定的數據進行實時同步。但是如果想要實現光標離開的時候再更新數據如何實現呢?
思路1: 綁定change事件,在事件回調中手動獲取target的值
思路2: 直接使用.lazy
修飾符即可達到效果
<template><div?class="lazy"><div?class="lazy-item"><input?type="text"?v-model="text"><p>無.lazy:?{{?text?}}</p></div><div?class="lazy-item"><input?type="text"?v-model.lazy="text2"><p>.lazy:?{{?text2?}}</p></div></div>
</template>export?default?{name:?'lazy',data?()?{return?{text:?'',text2:?''}}
}
可以看到添加了.lazy修飾符之后,第二個輸入框輸入的值不會實時反應在下面,而是光標離開實,text2
的數據才更新了

16 .number
我們知道
input
輸入框的type
哪怕是number
得到的值的類型也是string
,如果我們想直接拿到number
類型的數據,有不想麻煩的手動轉換應該怎么辦呢?
<template><div?class="number"><div?class="number-item"><p>無.number?</p><input?type="number"?v-model="number"></div><div?class="number-item"><p>type:text?.number?</p><input?type="text"?v-model.number="number1"></div><div?class="number-item"><p>type:number?.number?</p><input?type="number"?v-model.number="number2"></div></div>
</template>export?default?{name:?'lazy',data?()?{return?{number:?0,number1:?'',number2:?'',}},watch:?{number?(newVal)?{console.log(typeof?newVal,?newVal)},number1?(newVal)?{console.log(typeof?newVal,?newVal)},number2?(newVal)?{console.log(typeof?newVal,?newVal)},}
}
第一個輸入框的類型是number,但是得到的值是string
第二個輸入框的類型是text,但是添加了number修飾符,得到的值可以是number(如果這個值無法被?
parseFloat()
?解析,則會返回原始的值。)第三個輸入框的類型是number,最后得到的值也是number

系統修飾符
當點擊事件或者鍵盤事件需要系統鍵同時按下才觸發時
.ctrl
、.alt
、.shift
、.meta
可以幫大忙噢!
如下代碼
全局監聽keydown事件,嘗試看
.ctrl
、.alt
、.shift
、.meta
是否被按下分別給四個按鈕加上
.ctrl
、.alt
、.shift
、.meta
修飾符并配合點擊事件,驗證是否同時按下指定按鍵,再點擊才會生效
注明:電腦ctrl鍵 + 點擊估計和瀏覽器快捷配置沖突了,導致沒觸發
<template><div?class="system"><p>{{?msg?}}</p><div?class="buttons"><button?@click.ctrl="onClickButon('ctrl')">ctrl</button><button?@click.alt="onClickButon('alt')">alt</button><button?@click.shift="onClickButon('shift')">shift</button><button?@click.meta="onClickButon('meta')">meta</button></div></div>??
</template>export?default?{name:?'system',data?()?{return?{msg:?''}},mounted?()?{this.onListenSystemKeyDown()},methods:?{onListenSystemKeyDown?()?{document.addEventListener('keydown',?(event)?=>?{let?msg?=?'按下了'if?(event.ctrlKey)?{msg?+=?'ctrl鍵'}?else?if?(event.altKey)?{msg?+=?'alt鍵'}?else?if?(event.shiftKey)?{msg?+=?'shift鍵'}?else?if?(event.metaKey)?{msg?+=?'meta鍵'}?else?{msg?+=?'其他鍵'}this.msg?=?msg},?false)},onClickButon?(key)?{console.log(`只有同時按下${key}鍵,點擊事件才會發生`)}}
}

17 .ctrl
僅在按下ctrl按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
18 .alt
僅在按下alt按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
19 .shift
僅在按下shift按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
20 .meta
僅在按下meta按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
21 .exact
嚴格來說這
.exact
不屬于系統修飾符,只是上面例子的寫法有一個現象,同時按下幾個系統修飾鍵(例如alt和shift)既可以觸發.alt
也可以觸發.shift
。
還是用上面的例子,看一下下面的gif, 此時我同時按下了alt和shift,對應的兩個事件都可以觸發

只想某個系統修飾鍵按下時才觸發點擊
沒有任何系統修飾符被按下的時候才觸發點擊
要實現上面的需求.exact
就派上用場了,用上面的例子稍作改造
<template><div?class="extra"><p>{{?msg?}}</p><div?class="buttons"><button?@click.ctrl.exact="onClickButon('ctrl')">ctrl</button><button?@click.alt.exact="onClickButon('alt')">alt</button><button?@click.shift.exact="onClickButon('shift')">shift</button><button?@click.meta.exact="onClickButon('meta')">meta</button><button?@click.exact="onClickButon('非系統鍵')">非系統鍵</button></div></div>??
</template>export?default?{name:?'extra',data?()?{return?{msg:?''}},mounted?()?{this.onListenSystemKeyDown()},methods:?{onListenSystemKeyDown?()?{document.addEventListener('keydown',?(event)?=>?{let?msg?=?'按下了'if?(event.ctrlKey)?{msg?+=?'ctrl鍵'}?else?if?(event.altKey)?{msg?+=?'alt鍵'}?else?if?(event.shiftKey)?{msg?+=?'shift鍵'}?else?if?(event.metaKey)?{msg?+=?'meta鍵'}?else?{msg?+=?'其他鍵'}this.msg?=?msg},?false)},onClickButon?(key)?{console.log(`只有同時按下${key}鍵,點擊事件才會發生`)}}
}

按鍵修飾符
在監聽鍵盤事件時,我們經常需要檢查詳細的按鍵再執行對應的邏輯,vue也為我們內置了至少11+的按鍵修飾符。
如下代碼,我們分別給enter
、tab
、delete
等按鍵指定了keydown
事件,當在指定的輸入框中按下指定的鍵盤,會打印出enter
、tab
、delete
等,其他按鍵在輸入框中無法觸發該console
<template><div?class="key-modifiers"><div?class="key-modifiers-item">enter:<input?type="text"?@keydown.enter="onKeydown('enter')"></div><div?class="key-modifiers-item">tab:<input?type="text"?@keydown.tab="onKeydown('tab')"></div>??<div?class="key-modifiers-item">delete:<input?type="text"?@keydown.delete="onKeydown('delete')"></div>??<div?class="key-modifiers-item">esc:<input?type="text"?@keydown.esc="onKeydown('esc')"></div>??<div?class="key-modifiers-item">space:<input?type="text"?@keydown.space="onKeydown('space')"></div>?<div?class="key-modifiers-item">up:<input?type="text"?@keydown.up="onKeydown('up')"></div>??<div?class="key-modifiers-item">down:<input?type="text"?@keydown.down="onKeydown('down')"></div>?<div?class="key-modifiers-item">left:<input?type="text"?@keydown.left="onKeydown('left')"></div>??<div?class="key-modifiers-item">right:<input?type="text"?@keydown.right="onKeydown('right')"></div>??<div?class="key-modifiers-item">page-down:<input?type="text"?@keydown.page-down="onKeydown('page-down')"></div>??<div?class="key-modifiers-item">page-up:<input?type="text"?@keydown.page-up="onKeydown('page-up')"></div>??</div>
</template>export?default?{name:?'keyModifiers',methods:?{onKeydown?(keyName)?{console.log(keyName)}}
}

22 .enter
在按下enter按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
23 .tab
在按下tab按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
24 .delete
在按下delete按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
25 .esc
在按下esc按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
26 .space
在按下space按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
27 .up
在按下up按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
28 .down
在按下down按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
29 .left
在按下left按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
30 .right
在按下right按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
31 .page-down
在按下(fn + down)按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
32 .page-up
在按下(fn + up)按鍵時才觸發鼠標或鍵盤事件的監聽器,詳細例子請看上面
如何自定義按鍵修飾符
vue本身給我們內置了很多實用的按鍵修飾符,大部分情況下可以滿足我們的日常需求了,那么有沒有辦法可以自定義按鍵修飾符呢?
通過以下配置即可定義一個屬于我們自己的按鍵修飾符, 比如我們定義q為按下q的快捷鍵。
Vue.config.keyCodes?=?{q:?81
}<div?class="custom"><input?type="text"?@keydown.q="f1Keydown">
</div>export?default?{name:?'custom',methods:?{f1Keydown?()?{console.log('按下了q')}}
}

最近組建了一個江西人的前端交流群,如果你是江西人可以加我微信?ruochuan12?私信 江西?拉你進群。
推薦閱讀
1個月,200+人,一起讀了4周源碼
我歷時3年才寫了10余篇源碼文章,但收獲了100w+閱讀
老姚淺談:怎么學JavaScript?
我在阿里招前端,該怎么幫你(可進面試群)
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》10余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助1000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。
識別上方二維碼加我微信、拉你進源碼共讀群
今日話題
略。歡迎分享、收藏、點贊、在看我的公眾號文章~