Vue自定義指令

前言

Vue.js是一套構建用戶界面的漸進式框架(官方說明)。通俗點來講,Vue.js是一個輕量級的,易上手易使用的,便捷,靈活性強的前端MVVM框架。簡潔的API,良好健全的中文文檔,使開發者可以較容易的上手Vue框架。javascript

本系列文章將結合我的在使用Vue中的一些經(cai)驗(keng)和一些案例,對Vue框架掌握的部分知識進行輸出,同時也鞏固對Vue框架的理解。html

Vue自定義指令

簡述

Vue除了提供了默認內置的指令外,還容許開發人員根據實際狀況自定義指令,它的做用價值在於當開發人員在某些場景下須要對普通DOM元素進行操做的時候。前端

註冊自定義指令

Vue自定義指令和組件同樣存在着全局註冊和局部註冊兩種方式。先來看看註冊全局指令的方式,經過 Vue.directive( id, [definition] ) 方式註冊全局指令,第一個參數爲自定義指令名稱(指令名稱不須要加 v- 前綴,默認是自動加上前綴的,使用指令的時候必定要加上前綴),第二個參數能夠是對象數據,也能夠是一個指令函數。vue

<div id="app" class="demo">
    <!-- 全局註冊 -->
    <input type="text" placeholder="我是全局自定義指令" v-focus>
</div>
<script> Vue.directive("focus", { inserted: function(el){ el.focus(); } }) new Vue({ el: "#app" }) </script>
複製代碼

這個簡單案例當中,咱們經過註冊一個 v-focus 指令,實現了在頁面加載完成以後自動讓輸入框獲取到焦點的小功能。其中 inserted 是自定義指令的鉤子函數,後面的內容會詳細講解。java

全局註冊好了,那麼再來看看如何註冊局部自定義指令,經過在Vue實例中添加 directives 對象數據註冊局部自定義指令。node

<div id="app" class="demo">
    <!-- 局部註冊 -->
    <input type="text" placeholder="我是局部自定義指令" v-focus2>
</div>
<script> new Vue({ el: "#app", directives: { focus2: { inserted: function(el){ el.focus(); } } } }) </script>
複製代碼

鉤子函數

一個指令定義對象能夠提供以下幾個鉤子函數 (均爲可選):git

  • bind:只調用一次,指令第一次綁定到元素時調用。在這裏能夠進行一次性的初始化設置es6

  • inserted:被綁定元素插入父節點時調用 (僅保證父節點存在,但不必定已被插入文檔中)。github

  • update:所在組件的 VNode 更新時調用,可是可能發生在其子 VNode 更新以前。指令的值可能發生了改變,也可能沒有。可是你能夠經過比較更新先後的值來忽略沒必要要的模板更新 。web

  • componentUpdated:指令所在組件的 VNode 及其子 VNode 所有更新後調用。

  • unbind:只調用一次,指令與元素解綁時調用。

這段是從官方文檔copy來的,相信應該都一看就明白的。

那麼這幾個鉤子函數怎麼使用呢?先來看看鉤子函數的幾個參數吧。指令鉤子函數會被傳入如下參數:

  • el: 指令所綁定的元素,能夠用來直接操做 DOM,就是放置指令的那個元素。

  • binding: 一個對象,裏面包含了幾個屬性,這裏很少展開說明,官方文檔上都有很詳細的描述。

  • vnode:Vue 編譯生成的虛擬節點。

  • oldVnode:上一個虛擬節點,僅在 update 和 componentUpdated 鉤子中可用。

自定義指令也能夠傳遞多個值,能夠用javascript表達式字面量傳遞,看例子:

<div v-demo="{ color: 'white', text: 'hello!' }"></div>
<script> Vue.directive('demo', function (el, binding) { console.log(binding.value.color) // "white" console.log(binding.value.text) // "hello!" }) </script>
複製代碼

說了這麼多理論知識,那麼如今就來動手寫一個簡單的案例吧。假設這樣的看一個場景:當你在閱覽某網站的圖片時,可能會因爲圖片資源比較大而加載緩慢,須要消耗一小段時間來呈現到眼前,這個體驗確定是不太友好的(就像網站切換頁面,有時候會加載資源比較慢,爲了給用戶較好的體驗,通常都會先出一個正在加載的友好提示頁面),因此這個案例的功能就是在圖片資源還沒加載出來時,先顯示默認背景圖,當圖片資源真正加載出來了以後,再把真實圖片放置到對應的位置上並顯示出來。

<div id="app2" class="demo">
    <div v-for="item in imageList">
        <img src="../assets/image/bg.png" alt="默認圖" v-image="item.url">
    </div>
</div>
<script> Vue.directive("image", { inserted: function(el, binding) { //爲了真實體現效果,用了延時操做 setTimeout(function(){ el.setAttribute("src", binding.value); }, Math.random() * 1200) } }) new Vue({ el: "#app2", data: { imageList: [ { url: "http://consumer-img.huawei.com/content/dam/huawei-cbg-site/greate-china/cn/mkt/homepage/section4/home-s4-p10-plus.jpg" }, { url: "http://consumer-img.huawei.com/content/dam/huawei-cbg-site/greate-china/cn/mkt/homepage/section4/home-s4-watch2-pro-banner.jpg" }, { url: "http://consumer-img.huawei.com/content/dam/huawei-cbg-site/en/mkt/homepage/section4/home-s4-matebook-x.jpg" } ] } }) </script>
複製代碼

源碼解讀

Vuetify 框架庫中,有提供幾種自定義指令API,包括瀏覽器窗口縮放 v-resize,瀏覽器滾動條滑動 v-scroll 等自定義指令,如今就來學習一波 Vuetify 中自定義指令源碼吧。

v-resize 自定義指令

src/directives/resize.js 中,是 v-resize 自定義指令操做的核心代碼。

function inserted (el, binding) {
    //指令的綁定值,是一個function函數
    const callback = binding.value

    //延時執行函數的毫秒數
    const debounce = binding.arg || 200

    //禁止執行與事件關聯的默認動做
    const options = binding.options || { passive: true }

    let debounceTimeout = null
    const onResize = () => {
        clearTimeout(debounceTimeout)
        debounceTimeout = setTimeout(callback, debounce, options)
    }

    //監聽窗口縮放
    window.addEventListener('resize', onResize, options)

    //存儲監聽窗口縮放事件的參數,方便在unbind鉤子函數中解除事件綁定的時候使用到
    el._onResize = {
        callback,
        options
    }

    if (!binding.modifiers || !binding.modifiers.quiet) {
        onResize()
    }
}

//綁定的DOM元素被移除時觸發
function unbind (el, binding) {
    const { callback, options } = el._onResize

    window.removeEventListener('resize', callback, options)
    delete el._onResize
}

export default {
    //指令名稱
    name: 'resize',
    inserted,
    unbind
}
複製代碼

能夠看到,定義了 insertedunbind 兩個鉤子函數,unbind 鉤子函數是用來解除監聽事件的。inserted 鉤子函數中,綁定監聽了窗口縮放事件並執行回調函數,並採用簡單的函數防抖來防止操做過分頻繁,大體的流程就是這樣子的。

可能你會發現,上面的代碼中,採用的都是es6標準語法寫的,對於還不太熟悉es6語法的童鞋來講,可能閱讀起來會比較的吃力,那麼下面就轉換成es5語法來完整的實現這個指令的功能,可是建議仍是儘可能去熟悉es6標準語法,由於這是前端發展進程中的必然趨勢。

function insertedFn (el, binding) {
    var callback = binding.value;
    var debounce = 200;
    var options = {passive: true};
    var debounceTimeout = null;
    var onResize = function () {
        clearTimeout(debounceTimeout);
        debounceTimeout = setTimeout(callback, debounce, options);
    }

    window.addEventListener("resize", onResize, options);

    el._onResize = {
        callback: callback,
        options: options
    };
}

function unbindFn (el, binding) {
    var callback = el._onResize.callback;
    var options = el._onResize.options;
    window.removeEventListener("resize", callback, options);
    delete el._onResize;
}

Vue.directive("resize", {
    inserted: insertedFn,
    unbind: unbindFn
})
複製代碼

完整的案例能夠點擊這裏查看:v-resize自定義指令

Vuetify 中更多的自定義指令案例源碼均可以在 github 中找到,附上 Vuetify 的 github 地址:github.com/vuetifyjs/v…

總結

列舉的都是一些簡單的Vue自定義指令的知識,在實際項目中的不一樣場景會存在着各類坑。
Vue自定義指令還能夠在圖片懶加載的場景下使用,vue-lazyload 就是有利用自定義指令實現圖片懶加載的,可能的話,後面能夠分析一波 vue-lazyload 的源碼。

後記

本着學習和總結的態度寫的文章,文中有任何錯誤和問題,能夠在github上指出 issues 。文中的案例都放置在github上,地址:github.com/webproblem/…

相關文章
相關標籤/搜索