1. 程式人生 > >淺談 Vue 指令

淺談 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標籤,這就是

事件冒泡,所以假設上述例子中 a 標籤為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>&lt;alert-menu
        :menudata="menu"
        :e="eventObj"
        ref="menu"
        v-on:menuEvent="handle"&gt;
        &lt;/alert-menu&gt;
    </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 指令了。