學習 vue 的時候肯定會接觸指令,那么什么是指令呢?
-
-
在 vue 中提供了一些對於頁面和數據更為方便的輸出,這些操作就叫做指令,以 v-xxx 表示,比如 html 頁面中的屬性
<div v-xxx ></div>
-
-
-
比如在 angular 中 以 ng-xxx 開頭的就叫做指令
-
指令中封裝了一些 DOM 行為,結合屬性作為一個暗號,暗號有對應的值,根據不同的值,會進行相關 DOM 操作的綁定,即可以進行一些模板的操作
-
vue 中常用的一些內置 v- 指令
-
-
v-text:元素的 innerText 屬性,只能用在雙標簽中, 和{{ }}效果是一樣的,使用較少
-
v-html:元素的 innerHTML,其實就是給元素的 innerHTML 賦值
-
v-show:元素的顯示與隱藏,基於 css 樣式的切換。如果確定要隱藏,會給元素的 style 加上display: none
-
v-if:元素的插入和移除操作,相當於對元素的銷毀和創建。如果表達式的值為 false,會留下一個
<!---->
作為標記,若未來 v-if 的值是 true 了,就在這里插入元素(如果 if 有 else 就不要單獨留坑了)。 -
v-else-if:前一個相鄰元素必須有 v-if 或 v-else-if
-
v-else:前一個相鄰元素必須有 v-if 或 v-else-if,如果 v-if 和 v-else-if 都有對應的表達式,則 v-else 可以直接寫
-
v-for:用於循環渲染一組數據(數組或對象)。必須使用特定語法:v-for="alias in expression"。注:當 v-for 和 v-if 同處於一個節點時,v-for 的優先級比 v-if 更高。即 v-if 將運行在每個 v-for循環中
-
v-on:主要用來監聽 dom 時間,然后執行一些操作。簡寫為【@】
-
v-model:用於 input/textarea 等表單控件上創建雙向數據綁定。
-
v-bind:動態的綁定一個或多個屬性,常用於綁定 class,style,href 等。
-
v-once:組件和元素只渲染一次,當數據發生變化,也不會重新渲染。
-
v-if 和 v-show 的對比
-
-
v-if 是真正的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子組件適當的被銷毀和重建。
-
v-if 也是惰性的,如果在初始渲染時條件為假,則什么也不做,直到條件第一次為真時,才會開始渲染條件塊。
-
相比之下 v-show 就簡單的多,不管初始條件是什么,元素總是會被渲染,並且只是簡單的基於 css 進行切換。
-
一般來說,v-if 有更高的切換開銷,而 v-show 有更高的初始渲染開銷。因此,如果需要非常頻繁的切換,則使用 v-show 較好,如果在運行時條件很少改變,則使用 v-if 較好。
在實際的開發過程中,可能這些內置指令並不能滿足所有的需求,或者說想為元素附件一些特別的功能。這時候,就需要用到 Vue 給我們提供的一個強大又靈活的功能「 自定義指令 」。
二、自定義指令的鈎子函數
Vue 提供了自定義指令的5個鈎子函數:
-
-
bind:指令第一次綁定到元素時調用,只執行一次。在這里可以進行一次性的初始化設置。
-
inserted:被綁定的元素,插入到父節點的 DOM 中時調用(僅保證父節點存在)。
-
update:組件更新時調用。
-
componentUpdated:組件與子組件更新時調用。
-
unbind:指令與元素解綁時調用,只執行一次。
-
注意:
-
-
除 update 與 componentUpdated 鈎子函數之外,每個鈎子函數都含有 el、binding、vnode 這三個參數
-
在每個函數中,第一個參數永遠是 el, 表示被綁定了指令的那個 dom 元素,這個el 參數,是一個原生的 JS 對象,所以 Vue 自定義指令可以用來直接和 DOM 打交道
-
-
oldVnode 只有在 update 與 componentUpdated 鈎子中生效
-
除了 el 之外,binding、vnode 屬性都是只讀的
-
鈎子函數說白了也就是生命周期,即當一個指令綁定到一個元素上時,這個指令內部有5個生命周期事件函數。接下來創建一個案例來看看這幾個鈎子函數的觸發情況:
<p v-test>這是一段文字</p> export default { ... ... directives: { test: { bind () { console.log('bind') }, inserted () { console.log('inserted') }, update () { console.log('update') }, componentUpdated () { console.log('componentUpdated') }, unbind () { console.log('unbind') } } } }
結果:
頁面渲染時,觸發了 bind
和inserted
函數。那么另外三個鈎子函數什么時候會觸發呢?
關於 update 的官方解釋:
update
:所在組件的 VNode 更新時調用,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前后的值來忽略不必要的模板更新 (詳細的鈎子函數參數見下)。
有點疑惑,‘ 所在組件的 VNode ’ 是指當前綁定了該指令的 dom 元素嗎?如果是的話,是不是只要當前元素的狀態發生了變化就會觸發 update
呢?如下通過 v-show 來切換元素顯示隱藏:
<p v-test v-show="show">這是另外一段文字</p> <button @click="show = !show">toggle</button> export default { data () { return { show: true } } }
默認還是觸發 bind
和inserted
,當點擊按鈕切換元素的 display 時,結果如下:
即:改變元素的樣式的時候觸發了 update
和componentUpdated
函數。如果使用 v-if 會觸發哪個事件呢?
<p v-test v-if="show">這是另外一段文字</p>
<button @click="show = !show">toggle</button>
結果:
發現 unbind
被觸發,因為 v-if 是刪除或者重建 dom 元素,當指令綁定的元素被銷毀時,會觸發指令的 unbind
事件,新建顯示仍是觸發 bind
和inserted
。
總結:
-
-
bind():當指令綁定在 HTML 元素上時觸發
-
inserted():當指令綁定的元素插入到父節點中的時候觸發
-
update():當指令綁定的元素狀態/樣式、內容(這里指元素綁定的 vue 數據) 發生改變時觸發
-
componentUpdated():當 update() 執行完畢之后觸發
-
unbind():當指令綁定的元素從 dom 中刪除時觸發
-
舉幾個應用場景的栗子
(1、輸入框自動獲取焦點(官方示例)。
(2、點擊下拉菜單以外的區域隱藏菜單。
(3、輸入的郵箱、電話的校驗。
上面這幾個場合,在做項目的時候可以用其他方法代替,但是 vue 自定義指令能做到一處定義,全局調用,使得代碼簡潔高效,更便於維護。
指令的注冊方式和「過濾器」「混入」「組件」注冊的方式一樣都分為兩種:一是全局注冊,二是局部注冊。
三、全局指令
// 給元素添加隨機背景 <div v-bgcolor></div> Vue.directive('bgcolor', { bind: function(el, binding, vnode) { el.style.backgroundColor = "#" + Math.random().toString(16).slice(2,8); } })
注意:在定義的時候,指令的名稱前面不需要加 v- 前綴,在調用的時候,必須在指定的名稱前加上 v-前綴來進行調用
四、局部指令
// 和data, methods同級 methods: {}, directives: { bgcolor: { bind: function(el, binding) { el.style.backgroundColor = "#" + Math.random().toString(16).slice(2,8); } } }
我個人更傾向於使用全局注冊的方式,因為既然已經使用了自定義指令,應該是通用的可復用的。所以提供整個項目使用的指令才更有價值,而不僅僅只限於某個組件內部。如果單一地方使用直接把功能摟出來就行了,何必費這力氣。
五、帶參數的自定義指令
<div v-bgcolor='{color: 'orange'}'></div> Vue.directive('bgcolor', { bind: function(el, binding) { el.style.backgroundColor = binding.value.color; } })
六、函數簡寫
如果想在 bind 和 update 時觸發相同行為,而不關心其它的鈎子,可以這樣寫:
// 全局 Vue.directive('bgcolor', function (el, binding) { el.style.backgroundColor = binding.value }) // 局部 directives: { bgcolor: (el, binding) => { el.style.backgroundColor = binding.value } }
七、應用實例
熟悉指令的創建方式與參數之后,我們就用它來創建兩個實際案例。
-
通過指令來實現點擊空白處子菜單隱藏的功能,具體代碼如下:
// clickOutside.js export default { bind (el, binding) { const self = {} // 定義一個私有變量,方便在unbind中可以解除事件監聽 self.documentHandler = (e) => { if (el.contains(e.target)) { // 這里判斷綁定的元素是否包含點擊元素,如果包含則返回 return false } if (binding.value) { // 判斷指令中是否綁定了值 binding.value(e) // 如果綁定了函數則調用那個函數,此處 binding.value就是handleClose方法 } return true } document.addEventListener('click', self.documentHandler) }, unbind (el) { // 解除事件監聽 document.removeEventListener('click', self.documentHandler) delete self.documentHandler } }
在組件中使用:
<template> <div> <div v-show="isShow" v-clickoutside="handleClickOutside" @click="showOrHide"> 子菜單 ... </div> </div> </template> <script> import clickoutside from './js/clickOutside' export default { ... ... directives: { clickoutside }, data() { return { isShow: true, }; }, methods: { handleOutsideClick () { this.isShow = false } } } </script>
-
自定義指令來優化圖片的加載:圖片在加載過程中,未加載完成時使用灰色背景占位,圖片加載完后直接顯示
<template> <div> <!-- 參數不可以直接填寫url地址--> <img v-imgUrl='url' /> </div> </template> <script> export default { data () { return { url: '../src/assets/logo.png' } } } </script>
// 全局注冊 Vue.directive('imgUrl', function (el, binding) { el.style.backgroundColor = '#FEFEFE' //設置背景顏色 var img = new Image() img.src = binding.value // binding.value 指令后的參數 img.onload = function () { el.style.backgroundColor = '' el.src = binding.value } })