淺談 Vue 指令
談到 Vue 指令,我們腦海裡浮現的第一個疑問便是 指令 是什麼:
指令是告訴計算機從事某一特殊運算的程式碼。如:資料傳送指令、算術運算指令、位運算指令、程式流程控制指令、串操作指令、處理器控制指令。
那麼 Vue 指令又是什麼呢?是用來幹什麼的?作為一名攻城獅的我們又如何去使用它?
Vue 內建指令
1. 內建指令的使用
v-if
:根據其後表示式的 bool 值進行判斷是否渲染該元素v-show
:其後表示式的 bool 值為 false 時,對渲染的出標籤新增display:none;
的樣式v-else
:緊跟著v-if或者v-show一起使用v-for
:v-for的用法 person in people ,前者是後者的元素,類似於陣列的用法v-bind:(:)
:用於響應地更新 html 特性v-on:(@)
:用於監聽指定元素的 DOM 事件v-once
:一次性插入文字,隨後的重新渲染,元素/元件及其所有的子節點將被視為靜態內容並跳過。v-html
:輸出 {{ message }} 內包含 html 程式碼的資料
tips: 不能使用 v-html 來複合局部模板,因為 Vue 不是基於字串的模板引擎,反之,對於使用者介面(UI),元件更適合作為可重用和可組合的基本單位。另外,只對可信內容使用 HTML 插值,絕不要對使用者提供的內容使用插值,避免xss攻擊。
2. 修飾符
一般用於指出 v-on 指令以特殊方式繫結
事件修飾符
<!-- prevent 一般用來阻止標籤的點選預設行為,a 標籤的點選跳轉-->
<a v-on:submit.prevent="onSubmit">...</a>
<!--阻止事件冒泡-->
<div @click='doThis' style="width:100px;height: 100px; background: red;">
// 點選父元素
<a v-on:click.stop="doThis">點選子元素</a>
</div>
descripation:當點選父元素的時候,執行 doThis ,當點選子元素 a 的時候,這個點選動作不單單觸發了 a 標籤,同時也觸發了div標籤,這就是v-on:click='doThis'
,則 doThis 會被執行兩次,父元素和子元素都執行了一次 click 事件,而 .stop 則是阻止事件冒泡,再次點選 a 標籤,click 事件只會執行一次.
按鍵修飾符
Vue 允許為 v-on 在監聽鍵盤事件時新增按鍵修飾符:
<input v-on:keyup.13="submit">
記住所有的 keyCode 比較困難,所以 Vue 為最常用的按鍵提供了別名:
<input v-on:keyup.enter="submit">
<input @keyup.enter="submit">
按鍵別名包括:
.enter
.tab
.delete
(捕獲 “刪除” 和 “退格” 鍵) .esc
.space
.up
.down
.left
.right
.ctrl
.shift
.meta
(windows 鍵,mac-command 鍵,)
Vue自定義指令
Vue 推崇資料驅動檢視的理念(資料互動,狀態管理),但並非所有情況都適合資料驅動( DOM 的操作)。自定義指令就是一種有效的補充和擴充套件,不僅可用於定義任何的 DOM 操作,並且是可複用的。
1. 定義Vue指令的方法
Vue.directive(id,definition)
description: 傳入兩個引數,指令ID和定義物件,定義物件提供了一些鉤子函式。
2. 鉤子函式
Vue.directive('my-directive', {
bind: function(){
// 指令第一次繫結到元素時呼叫,做繫結的準備工作
// 比如新增事件監聽器,或是其他只需要執行一次的複雜操作
},
inserted: function(){
// 被繫結標籤的父節點加入 DOM 時立即觸發
},
update: function(){
// 根據獲得的新值執行對應的更新
// 對於初始值也會呼叫一次
},
componentUpdated: function(){
// 指令所在元件的 VNode 及其子 VNode 全部更新後呼叫,一般使用 update 即可
},
unbind: function(){
// 做清理操作
// 比如移除bind時繫結的事件監聽器
}
})
當只是用到 update 函式的時候,可以簡化寫法
Vue.directive('my-directive', function(){
// update 內的程式碼塊
})
目前,對 5個鉤子函式的觸發時機有了初步的認識。存疑的是 bind 和 inserted、update 和 componentUpdated 的區別了。
- bind 和 inserted 的區別
<div id="app">
<input v-focus>
</div>
<script>
// 註冊一個全域性自定義指令v-focus
Vue.directive('focus', {
// 當繫結元素插入到DOM中
inserted: function (el) {
// 聚焦元素
el.focus()
}
//聚焦不到元素
bind: function(el){
el.focus()
}
});
var app = new Vue({
el: '#app'
});
</script>
description: 以上例子中,如果將程式碼寫在 bind
鉤子函式內,el.focus()
並未生效,這是因為在 bind
鉤子函式被呼叫時,雖然能夠通過 bind 的第一個引數 el 拿到對應的 DOM 元素,但是此刻該 DOM 元素還未被插入進 DOM 樹中,因此在這個時候執行 el.focus()
是無效的。
當 DOM 元素被插入進 DOM 樹中時,inserted
鉤子就會被呼叫,因此在 inserted 中執行 el.focus()
是可以生效的。
- update 和 componentUpdated 的區別
update: function (el, binding, vnode) {
console.log('update')
console.log(el.innerHTML) // Hello
},
componentUpdated: function (el, binding, vnode) {
console.log('componentUpdated')
console.log(el.innerHTML) // Hi
}
update
鉤子函式觸發時機是自定義指令所在元件的 VNode 更新時, componentUpdated
觸發時機是指令所在元件的 VNode 及其子 VNode 全部更新後。此處使用 el.innerHTML
獲取 data 值,從執行結果上看 update 和 componentUpdated 是 DOM 更新前和更新後的區別。
3. 引數所包含屬性的意義
所有的鉤子函式會被複制到實際的指令物件中,而這個指令物件將會是所有鉤子函式的this上下文環境。指令物件上暴露了一些有用的公開屬性。
-
el: 指令所繫結的元素,可以用來直接操作DOM 。
-
binding: 一個物件,包含以下屬性:
-
name: 指令名,不包括 v- 字首。
-
value: 指令的繫結值, 例如:
v-directive="1 + 1"
,value 的值是2
。 -
oldValue: 指令繫結的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。
-
expression: 繫結值的字串形式。 例如
v-directive="1 + 1"
, expression 的值是"1 + 1"
。 -
arg: 傳給指令的引數。例如
v-directive:foo, arg
的值是"foo"
。 -
modifiers: 一個包含修飾符的物件。 例如:
v-directive.foo.bar
, 修飾符物件 modifiers 的值是{ foo: true, bar: true }
。 -
vnode: Vue 編譯生成的虛擬節點。
-
oldVnode: 上一個虛擬節點,僅在 update 和 componentUpdated 鉤子中可用。
tips: 這些屬性是隻讀的,不要修改它們。你也可以給指令物件附加自定義的屬性,但是注意不要覆蓋已有的內部屬性。
eg: 定義一個使用了 binding 引數的指令,以下的是都是生成的虛擬的節點,插入到 div:#example3
節點中:
<div id="example3" v-parameter:red="message"></div>
Vue.directive('parameter', {
bind: function(el, binding, vnode){
el.style.color = '#fff'
el.style.backgroundColor = binding.arg
el.innerHTML ='指令名name - '+ binding.name + '<br>' +'指令繫結值value - '+ binding.value + '<br>' +'指令繫結表示式expression - ' + binding.expression + '<br>'+'傳入指令的引數argument - '+ binding.arg + '<br>'
},
});
var demo = new Vue({
el: '#example3',
data: {
message: 'hello,v-parameter'
}
})
// 執行結果為(實際執行結果背景色應該為紅色,字型顏色應該為白色)
// "指令名 name-parameter"
// "指令繫結值 value-hello,v-parameter!"
// "指令繫結表示式 express-message"
// "傳入指令的引數 argument-red"
Vue自定義指令優先順序順序
- 系統預設指令會先於自定義指令執行
- 自定義指令在標籤上的位置越靠前就越早執行
<!-- v-show 先於 v-block 執行 -->
<div v-block v-show="false"></div>
<!-- v-none 先於 v-block 執行 -->
<div v-none v-block></div>
定義這兩個簡單的指令
Vue.directive("block",{
inserted:function (el) {
el.style.display = "block";
}
})
Vue.directive("none",{
inserted:function (el) {
el.style.display = "none";
}
})
Vue指令的用途
1. 用來操作DOM
儘管Vue推崇資料驅動檢視的理念,但並非所有情況都適合資料驅動。自定義指令就是一種有效的補充和擴充套件,不僅可用於定義任何的 DOM 操作,並且是可複用的。
eg: 很多時候我們會遇到圖片載入慢的問題,那麼,在圖片未完成載入前,可以用隨機的背景色佔位,圖片載入完成後才直接渲染出來。˙這裡,用自定義指令可以非常方便的實現這個功能。
tips: 本例的除錯需要在控制檯上如下操作:Network
-> Offline Oline
-> Slow 3G
,在網路延遲的情況下更容易看出佔位效果來。
Vue.directive('img',{
//DOM
inserted:function(el,binding){
var color =Math.floor(Math.random()*1000000);
el.style.backgroundColor = '#' + color;
var img = new Image();
img.src = binding.value;
img.onload = function(){
el.style.backgroundImage = 'url(' + binding.value + ')';
}
}
})
<div v-img="val.url" v-for="val in list"></div>
//此處圖片路徑為示意結果,為了能夠更好的看出本段測試程式碼的效果,建議大家選擇網上比較高清的圖片
list:[
{url:'1.jpg'},
{url:'1.jpg'},
{url:'1.jpg'}
]
2. 用於整合第三方外掛
我們知道任何軟體開發領域都可以分為四層:底層是原生的API,上層是通用框架,再上層是通用元件,最上層才是具體的業務程式碼。一個通用框架,必須搭配一套完整的通用元件,才能夠很快的被廣泛認可。
在前端開發領域,以前的通用框架是 jQuery,jQuery 以及基於 jQuery 構建的通用元件形成了一個龐大的生產系統。現在的通用框架是 Angular、React和Vue ,每個框架都需要基於自身構建新的元件庫。自定義指令好就好在:原先的那些通用元件,無論是純js的也好,基於 jQuery 的也好,都可以拿來主義直接吸收,而不需要改造或重構。
eg: 寫文件通常會用到 highlight.js,我們可以直接將其封裝為一個自定義指令,這樣 highlight.js 就變成了 Vue 的一個新功能。
var hljs = require('highlight.js');
Vue.directive('highlight',function(el){
hljs.hightlightBlock(el);
})
<pre>
<code v-hightlight><alert-menu
:menudata="menu"
:e="eventObj"
ref="menu"
v-on:menuEvent="handle">
</alert-menu>
</code>
</pre>
執行結果:
輸出<alert-menu>
標籤裡的所有內容,而且按照 html 的高亮顯示規則顯示。
tips: 所以但凡遇到第三方外掛如何與 Vue.js 整合的問題,都可以嘗試用自定義指令實現。
Vue指令和Vue元件之間的關係
很多時候,對於初學者來說,看完指令的使用會發現元件的使用和指令的自定義有幾分相似之處。其實,並非如此,元件和指令完全不是一個層級上的概念。打個比方:元件是一個房子,它可以巢狀使用,房子裡邊又有窗戶,門,桌子,床,櫃子等這些子元件。而指令是附著在元件上的某種行為或者功能,門和窗戶可以開啟關閉,桌子可以摺疊,櫃子可以開啟關上等等。以下是對於元件和指令的定義,希望能夠讓大家更清晰的理解:
- 元件:一般是指一個獨立實體,元件之間的關係通常都是樹狀。
- Vue指令:用以改寫某個元件的預設行為,或者增強使其獲得額外功能,一般來說可以在同一個元件上疊加若干個指令,使其獲得多種功能。比如 v-if,它可以安裝或者解除安裝元件。
最佳實踐
根據需求的不同,我們要選擇恰當的時機去初始化指令、更新指令呼叫引數以及釋放指令存在時的記憶體佔用等。一個健壯的庫通常會包含:初始化例項、引數更新和釋放例項資源佔用等操作。
Vue.directive('hello', {
bind: function (el, binding) {
// 在 bind 鉤子中初始化庫例項
// 如果需要使用父節點,也可以在 inserted 鉤子中執行
el.__library__ = new Library(el, binding.value)
},
update: function (el, binding) {
// 模版更新意味著指令的引數可能被改變,這裡可以對庫例項的引數作更新
// 酌情使用 update 或 componentUpdated 鉤子
el.__library__.setOptions(Object.assign(binding.oldValue, binding.value))
},
unbind: function (el) {
// 釋放例項
el.__library__.destory()
}
})
總結回顧
通過以上 Vue 指令的學習,以及諸多 demo 的實現,我們便可清晰的認識 Vue 指令了,並且能逐一解答我們最開始內心的疑慮了:
- Vue 指令就是以 “v-” 開頭,作用於 DOM ,為 DOM 新增特殊行為的一種指令。
- 自定義指令則是對 Vue 指令的一種有效的補充和擴充套件,不僅可用於定義任何的 DOM 操作,並且是可複用的。
- Vue 指令使用大部分是和元件結合著使用,從而增強元件的功能。甚至部分 Vue 指令能夠直接安裝解除安裝元件,例如
v-if
。 - Vue 自定義指令最重要的是我們對
bind
inserted
update
componentUpdated
unbind
五個鉤子函式的理解,以及對鉤子函式中el
binding
(屬性值)vnode
oldVnode
四個引數的使用,只要這些內容能夠熟練使用,我們便可以自己去定義適合各種開發場景的 Vue 指令了。