Vue自定義指令

惆悵客發表於2017-12-20

前言

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

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

Vue自定義指令

簡述

Vue除了提供了預設內建的指令外,還允許開發人員根據實際情況自定義指令,它的作用價值在於當開發人員在某些場景下需要對普通DOM元素進行操作的時候。

註冊自定義指令

Vue自定義指令和元件一樣存在著全域性註冊和區域性註冊兩種方式。先來看看註冊全域性指令的方式,通過 Vue.directive( id, [definition] ) 方式註冊全域性指令,第一個引數為自定義指令名稱(指令名稱不需要加 v- 字首,預設是自動加上字首的,使用指令的時候一定要加上字首),第二個引數可以是物件資料,也可以是一個指令函式。

<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 是自定義指令的鉤子函式,後面的內容會詳細講解。

全域性註冊好了,那麼再來看看如何註冊區域性自定義指令,通過在Vue例項中新增 directives 物件資料註冊區域性自定義指令。

<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>
複製程式碼

鉤子函式

一個指令定義物件可以提供如下幾個鉤子函式 (均為可選):

  • bind:只呼叫一次,指令第一次繫結到元素時呼叫。在這裡可以進行一次性的初始化設定

  • inserted:被繫結元素插入父節點時呼叫 (僅保證父節點存在,但不一定已被插入文件中)。

  • update:所在元件的 VNode 更新時呼叫,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前後的值來忽略不必要的模板更新 。

  • 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/…

相關文章