6個實用的Vue自定義指令推薦

在 Vue2.0,除了核心功能默認內置的指令 ( v-model 和 v-show ),Vue 也容許註冊自定義指令。在 Vue2.0 中,代碼複用和抽象的主要形式是組件。然而,有的狀況下,你仍然須要對普通 DOM 元素進行底層操做,這時候就會用到自定義指令。javascript

Vue 自定義指令有全局註冊和局部註冊兩種方式。全局註冊指令的方式,經過 Vue.directive( id, [definition] ) 方式註冊全局指令。若是想註冊局部指令,組件中也接受一個directives的選項。css

// 註冊一個全局自定義指令 `v-focus`
Vue.directive('focus', {
  // 當被綁定的元素插入到 DOM 中時……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

// 註冊一個局部自定義指令 `v-focus`
directives: {
  focus: {
    // 指令的定義
    inserted: function (el) {
      el.focus()
    }
  }
}
複製代碼

而後咱們能夠在模板中任何元素上使用心得v-focus property,以下:html

<input v-focus>
複製代碼

當咱們須要批量註冊自定義指令時,寫不少個``Vue.directive( id, [definition] ) 會致使代碼冗餘,因此咱們能夠利用Vue.use()` 的特性,完成批量註冊。前端

批量註冊指令,新建 directives/directive.js 文件vue

// 導入指令定義文件
import debounce from './debounce'
import throttle from './throttle'
// 集成一塊兒
const directives = {
  debounce,
  throttle,
}
//批量註冊
export default {
  install(Vue) {
    Object.keys(directives).forEach((key) => {
      Vue.directive(key, directives[key])
    })
  },
}

複製代碼

main.js 引入,並Vue.use() 調用完成批量註冊。java

import Vue from 'vue'
import Directives from './directives/directive.js'
Vue.use(Directives)
複製代碼

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

  • bind: 只調用一次,指令第一次綁定到元素時調用,能夠定義一個在綁定時執行一次的初始化動做,此時獲取父節點爲null。
  • inserted: 被綁定元素插入父節點時調用(僅保證父節點存在,但不必定已被插入文檔中),此時能夠獲取到父節點。
  • update: 所在組件的 VNode 更新時調用,可是可能發生在其子 VNode 更新以前。指令的值可能發生了改變,也可能沒有。可是你能夠經過比較更新先後的值來忽略沒必要要的模板更新
  • componentUpdated: 指令所在組件的 VNode 及其子 VNode 所有更新後調用。
  • unbind: 只調用一次, 指令與元素解綁時調用。

接下來咱們來看一下鉤子函數的參數 (即 elbindingvnodeoldVnode)。express

指令鉤子函數會被傳入如下參數:後端

  • el:指令所綁定的元素,能夠用來直接操做 DOM。
  • binding:一個對象,包含如下 property:
    • name:指令名,不包括 v- 前綴。
    • value:指令的綁定值,例如:v-my-directive="1 + 1" 中,綁定值爲 2
    • oldValue:指令綁定的前一個值,僅在 updatecomponentUpdated 鉤子中可用。不管值是否改變均可用。
    • expression:字符串形式的指令表達式。例如 v-my-directive="1 + 1" 中,表達式爲 "1 + 1"
    • arg:傳給指令的參數,可選。例如 v-my-directive:foo 中,參數爲 "foo"
    • modifiers:一個包含修飾符的對象。例如:v-my-directive.foo.bar 中,修飾符對象爲 { foo: true, bar: true }
  • vnode:Vue 編譯生成的虛擬節點。移步 VNode API 來了解更多詳情。
  • oldVnode:上一個虛擬節點,僅在 updatecomponentUpdated 鉤子中可用。

注意:除了 el 以外,其它參數都應該是隻讀的,切勿進行修改。若是須要在鉤子之間共享數據,建議經過元素的 dataset 來進行。api

下面分享幾個實用的 Vue 自定義指令

  • 長按指令 v-longpress
  • 函數防抖指令 v-debounce
  • 函數節流指令 v-throttle
  • 點擊元素外部指令 v-click-out
  • 彈窗限制外部滾動指令 v-scroll-pop
  • 神策埋點指令v-sensor

1 v-longpress

需求:當用戶按下鼠標左鍵或移動端單指觸碰,並按住按鈕幾秒鐘時,視爲一次長按,觸發對應的函數。

思路:

  1. 定義一個計時器, n 秒後執行函數,n做爲參數。
  2. 當用戶按下按鈕時觸發 mousedowntouchstart 事件,啓動計時器。
  3. 若是 clickmouseuptouchendtouchcancel 事件在 n 秒內被觸發,則清除計時器,視爲普通點擊事件。
  4. 若是計時器沒有在 n秒內清除,則視爲一次長按,觸發對應的函數。
const longpress = {
  bind: function (el, {value:{fn,time}}) {
    //沒綁定函數直接返回
    if (typeof fn !== 'function') return
    // 定義定時器變量
    el._timer = null
    // 建立計時器( n秒後執行函數 )
    el._start = (e) => {
      //e.type表示觸發的事件類型如mousedown,touchstart等
      //pc端: e.button表示是哪一個鍵按下0爲鼠標左鍵,1爲中鍵,2爲右鍵
      //移動端: e.touches表示同時按下的鍵爲個數
      if (  (e.type === 'mousedown' && e.button && e.button !== 0) || 
            (e.type === 'touchstart' && e.touches && e.touches.length > 1)
      ) return;
      //定時長按n秒後執行事件
      if (el._timer === null) {
        el._timer = setTimeout(() => {
          fn()
        }, time)
        //取消瀏覽器默認事件,如右鍵彈窗
        el.addEventListener('contextmenu', function(e) {
          e.preventDefault();
        })
      }
    }
    // 若是兩秒內鬆手,則取消計時器
    el._cancel = (e) => {
      if (el._timer !== null) {
        clearTimeout(el._timer)
        el._timer = null
      }
    }
    // 添加計時監聽
    el.addEventListener('mousedown', el._start)
    el.addEventListener('touchstart', el._start)
    // 添加取消監聽
    el.addEventListener('click', el._cancel)
    el.addEventListener('mouseout', el._cancel)
    el.addEventListener('touchend', el._cancel)
    el.addEventListener('touchcancel', el._cancel)
  },
  // 指令與元素解綁時,移除事件綁定
  unbind(el) {
    // 移除計時監聽
    el.removeEventListener('mousedown', el._start)
    el.removeEventListener('touchstart', el._start)
    // 移除取消監聽
    el.removeEventListener('click', el._cancel)
    el.removeEventListener('mouseout', el._cancel)
    el.removeEventListener('touchend', el._cancel)
    el.removeEventListener('touchcancel', el._cancel)
  },
}

export default longpress

複製代碼

使用:給 Dom 加上 v-longpress 及參數便可

<template>
  <button v-longpress="{fn: longpress,time:2000}">長按</button>
</template>

<script> export default { methods: { longpress () { console.log('長按指令生效') } } } </script>

複製代碼

2 v-debounce

背景:在開發中,有時遇到要給input或者滾動條添加監聽事件,須要作防抖處理。

需求:防止input或scroll事件在短期內被屢次觸發,使用防抖函數限制必定時間後觸發。

思路:

  1. 定義一個延遲執行的方法,若是在延遲時間內再調用該方法,則從新計算執行時間。
  2. 將事件綁定在傳入的方法上。
const debounce = {
    inserted: function (el, {value:{fn, event, time}}) {
      //沒綁定函數直接返回
      if (typeof fn !== 'function') return
      el._timer = null
      //監聽點擊事件,限定事件內若是再次點擊則清空定時器並從新定時
      el.addEventListener(event, () => {
        if (el._timer !== null) {
          clearTimeout(el._timer)
          el._timer = null
        }
        el._timer = setTimeout(() => {
          fn()
        }, time)
      })
    },
  }
  
  export default debounce
  
  
複製代碼

使用:給 Dom 加上 v-debounce 及回調函數便可

<template>
  <input v-debounce="{fn: debounce, event: 'input', time: 5000}" />
      <div v-debounce="{fn: debounce, event: 'scroll', time: 5000}">
          <p>文字文字文字文字...</p>
      </div>
</template>

<script> export default { methods: { debounce(){ console.log('debounce 防抖') }, } } </script>

複製代碼

3 v-throttle

背景:在開發中,有些提交保存按鈕有時候會在短期內被點擊屢次,這樣就會屢次重複請求後端接口,形成數據的混亂,好比當即購買按鈕,屢次點擊就會屢次調用建立訂單接口。

需求:防止按鈕在短期內被屢次點擊,使用節流函數限制規定時間內只能點擊一次。

思路:

  1. 定義一個由開關(默認爲開)控制是否執行的方法,第一次執行函數時將開關關閉,在規定時間內再調用該方法,則不會再次執行,直至規定時間事後開關打開。
  2. 將事件綁定在 click 方法上。
const throttle = {
    bind:function (el,{value:{fn,time}}) {
        if (typeof fn !== 'function') return
        el._flag = true;//開關默認爲開
        el._timer = null
        el.handler = function () {
            if (!el._flag) return;
            //執行以後開關關閉
            el._flag && fn()
            el._flag = false
            if (el._timer !== null) {
                clearTimeout(el._timer)
                el._timer = null
            }
            el._timer = setTimeout(() => {
                el._flag = true;//三秒後開關開啓
            }, time);
        }
        el.addEventListener('click',el.handler)
    },
    unbind:function (el,binding) {
        el.removeEventListener('click',el.handler)
    }
}

export default throttle
複製代碼

使用:給Dom加上v-throttle 及回調函數便可。

<template>
 <button v-throttle="{fn: throttle,time:3000}">throttle節流</button>
</template>

<script> export default { methods: { throttle () { console.log('throttle 節流 只觸發一次') } } } </script>
複製代碼

4 v-clickOut

背景:在咱們的項目裏,常常會出現一個彈窗,須要點擊彈窗外部關閉該彈窗。

需求:實現一個指令,點擊目標區域外部,觸發指定函數。

思路:

  1. 判斷點擊的元素是否爲目標元素,是則不做爲,不然觸發指定函數。
const clickOut = {
    bind(el,{value}){
        function clickHandler(e) {
            //先判斷點擊的元素是不是自己,若是是自己,則返回
            if (el.contains(e.target)) return;
            //判斷指令中是否綁定了函數
            if (typeof value === 'function') {
                //若是綁定了函數,則調用函數,此處value就是clickImgOut方法
                value()
            }
        }
        // 給當前元素綁定個私有變量,方便在unbind中能夠解除事件監聽
        el.handler = clickHandler;
        //添加事件監聽
        setTimeout(() => {
            document.addEventListener('click',el.handler);
        }, 0);
    },
    unbind(el){
        //解除事件監聽
        document.removeEventListener('click',el.handler);
    }
}

export default clickOut

複製代碼

使用,將須要用到該指令的元素添加 v-click-out

<template>
  <div>
 		<button @click="isImgShow = true">展現彈窗</button>
    <div v-click-out="clickImgOut" v-if="isImgShow" class="pop">
          <img src="https://xxx.jpg" alt="">
          <p>文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字</p>
    </div>
  </div>
</template>

<script> export default { data(){ return { isImgShow : false } }, methods:{ clickImgOut(){ this.isImgShow = false; console.log('點擊彈窗外部') } } } </script>
複製代碼

5 v-scrollPop

背景:在咱們的項目中,常用彈窗展現活動規則,活動規則過長鬚要滾動時,時長會致使外部滾動。這時針對這種狀況,咱們能夠經過全局自定義指令來處理。

需求:自定義一個指令,使得彈窗內部內容能夠滾動,外部沒法滾動。

思路:

  1. 當彈窗展現時,記錄滾動條滾動距離,而後給body和html設置固定定位,高度100%,top值爲滾動距離。
  2. 當彈窗解除時,恢復原先樣式,並把滾動距離設置成原來的值。
const scrollPop = {
    bind(el) {
        //定義此時到元素的內容垂直滾動的距離
        el.st = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
        let cssStr = `overflow: hidden;width: 100%; height: 100%; position: fixed; top: ${- el.st}px;`
        document.querySelector('html').cssText = cssStr
        document.body.style.cssText = cssStr
    },
    unbind(el,{value}) {
        let cssStr = 'overflow: auto; height: 100%; position: relative; top: 0px;scroll-behavior: auto'
        document.querySelector('html').cssText = cssStr
        document.body.style.cssText = cssStr
        document.querySelector('html').style.scrollBehavior = 'auto'
        //手動設置滾動距離
        document.documentElement.scrollTop = el.st
        document.body.scrollTop = el.st
        if (value !== 'smooth')return;
        //若是傳了滾動方式爲smooth平穩滾動即有感滾動,當滾動完畢後,把auto改回smooth
        let timer = setTimeout(() => {
            cssStr = `overflow: auto; height: 100%; position: relative; top: 0px; scroll-behavior: ${value||'smooth'}`
            document.querySelector('html').cssText = cssStr
            document.querySelector('html').style.scrollBehavior = value || 'smooth'
            document.body.style.cssText = cssStr
        }, 1);
    }
}

export default scrollPop

複製代碼

使用:給須要限制的彈窗綁定v-scroll-pop屬性,並設置scroll-behavior 值便可。

<div class="scroll-pop" v-if="isScrollPopShow" v-scroll-pop="'smooth'">
	<div class="content">
    <p>這是很長一段文字,請耐心讀完,而後你會發現這段文字並無什麼意義。</p>
    ...
  </div>
</div>

複製代碼

6 v-sensor

背景:目前前端埋點代碼大量入侵業務,埋點代碼量大且難以區分和維護,現作出優化方案以減小其代碼量。

埋點類型:

  • ElementShow:頁面元素顯示
  • PopupTrack:彈窗顯示
  • $WebClick:點擊頁面按鈕
  • PopupBtnClick:點擊彈窗中按鈕
  • 自定義事件

優化方案:

1.自定義指令 v-sensor=" {el :'Btn_XXX_Tag_Common',elClick:'Btn_XXX_Tag_Common'} "

註冊封裝自定義指令的代碼

const sensor = {
    // 當被綁定的元素插入到 DOM 中時
    inserted: function (el,{value: sensorObj}) {
        let showObj={} ,clickObj={}//showObj表明展現類埋點,clickObj表明點擊類埋點
        //若是傳入參數格式不爲對象,則不向下執行
        if (!Object.prototype.toString.call(sensorObj) === '[object Object]'|| JSON.stringify(sensorObj) == "{}") return
        //遍歷傳入對象參數,根據key值肯定埋點類型
        for (const key in sensorObj) {
            if (Object.hasOwnProperty.call(sensorObj, key)) {
                switch (key) {
                    case 'el':
                        showObj= {
                            name:'ElementShow',
                            value: sensorObj[key]
                        };
                        break;
                    case 'pop':
                        showObj= {
                            name:'PopupTrack',
                            value: sensorObj[key]
                        };
                        break;
                    case 'elClick':
                        clickObj= {
                            name:'$WebClick',
                            value: sensorObj[key]
                        };
                        break;
                    case 'popClick':
                        clickObj= {
                            name:'PopupBtnClick',
                            value: sensorObj[key]
                        };
                        break;  
                    default:
                        break;
                }
            }
        }
        // 展現類埋點執行
        showObj.value && sensors.track(showObj.name, {
            FileName: showObj.value
        });
        //點擊類埋點執行
        if (clickObj.value) {
            el.handler = function () {
                clickObj.name === '$WebClick' && sensors.track(clickObj.name, {
                    $element_name: clickObj.value
                });
                clickObj.name === 'PopupBtnClick' && sensors.track(clickObj.name, {
                    FileName: clickObj.value
                });
            }
            el.addEventListener('click',el.handler)
        }
    },
    // 指令與元素解綁的時候,移除事件綁定
    unbind(el) {
        el.handler && el.removeEventListener('click', el.handler)
    }
}
  
  export default sensor
    
  
複製代碼

​ 對於除自定義事件之外的埋點事件,較好的優化辦法就是使用自定義指令。使用 v-sensor=" {el :'Btn_XXX_Tag_Common',elClick:'Btn_XXX_Tag_Common'} " 。v-sensor接收一個對象做爲參數,對象的key爲事件標識,對象的value爲事件屬性,key值具體對應關係以下。

  • el:ElementShow
  • pop:PopupTrack
  • elClick:$WebClick
  • popClick:PopupBtnClick
//單獨使用ElementShow或$WebClick
<div v-sensor="{el :'Btn_XXX_Tag_CXXXon'}">我是一個麼得感情的標籤</div>
<div v-sensor="{elClick:'Btn_XXX_Tag_Common'}">俺也同樣</div>
//ElementShow和$WebClick組合使用方法
<div v-sensor="{el :'Btn_XXX_Tag_Common',elClick:'Btn_XXX_Tag_Common'}">俺也同樣</div>
//單獨使用PopupTrack和PopupBtnClick
<div v-sensor="{pop :'Pop_XXX_Tag_Common'}">俺也同樣</div>
<div v-sensor="{popClick:'Pop_XXX_Tag_Common'}">俺也同樣</div>
//PopupTrack和PopupBtnClick組合使用方法
<div v-sensor="{pop :'Pop_XXX_Tag_Common',popClick:'Pop_XXX_Tag_Common'}">俺也同樣</div>
//變量使用方法
<div v-sensor="{pop :`${sensorVal}`}">俺也同樣</div>
複製代碼

提示:

​ 因爲該自定義指令是在元素插入頁面DOM中時執行的,因此若是事件屬性值使用變量的話,請在created生命週期內操做完畢,或給該元素綁定v-if爲對應變量。

本期分享結束,謝謝你們~~~

相關文章
相關標籤/搜索