vue自定義指令

如何自定義指令

注冊(cè)一個(gè)自定義指令有全局注冊(cè)與局部注冊(cè)

全局注冊(cè)注冊(cè)主要是用過(guò) Vue.directive 方法進(jìn)行注冊(cè)

Vue.directive 第一個(gè)參數(shù)是指令的名字(不需要寫(xiě)上 v-前綴),第二個(gè)參數(shù)可以是對(duì)象數(shù)據(jù),也可以是一個(gè)指令函數(shù)

// 注冊(cè)一個(gè)全局自定義指令 `v-focus`
Vue.directive("focus", {
  // 當(dāng)被綁定的元素插入到 DOM 中時(shí)……
  inserted: function (el) {
    // 聚焦元素
    el.focus(); // 頁(yè)面加載完成之后自動(dòng)讓輸入框獲取到焦點(diǎn)的小功能
  },
});

局部注冊(cè)通過(guò)在組件options選項(xiàng)中設(shè)置directive屬性

directives: {
  focus: {
    // 指令的定義
    inserted: function (el) {
      el.focus() // 頁(yè)面加載完成之后自動(dòng)讓輸入框獲取到焦點(diǎn)的小功能
    }
  }
}

鉤子函數(shù)

自定義指令也像組件那樣存在鉤子函數(shù):

  • bind:只調(diào)用一次,指令第一次綁定到元素時(shí)調(diào)用。在這里可以進(jìn)行一次性的初始化設(shè)置
  • inserted:被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用 (僅保證父節(jié)點(diǎn)存在,但不一定已被插入文檔中)
  • update:所在組件的 VNode 更新時(shí)調(diào)用,但是可能發(fā)生在其子 VNode更新之前。指令的值可能發(fā)生了改變,也可能沒(méi)有。但是你可以通過(guò)比較更新前后的值來(lái)忽略不必要的模板更新
  • componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用
  • unbind:只調(diào)用一次,指令與元素解綁時(shí)調(diào)用

所有的鉤子函數(shù)的參數(shù)都有以下:

  1. el:指令所綁定的元素,可以用來(lái)直接操作 DOM

  2. binding:一個(gè)對(duì)象,包含以下 property:

    name:指令名,不包括 v- 前綴。

    value:指令的綁定值,例如:v-my-directive="1 + 1" 中,綁定值為 2。

    oldValue:指令綁定的前一個(gè)值,僅在 update 和 componentUpdated 鉤子中可用。無(wú)論值是否改變都可用。

    expression:字符串形式的指令表達(dá)式。例如 v-my-directive="1 + 1" 中,表達(dá)式為 "1 + 1"。

    arg:傳給指令的參數(shù),可選。例如 v-my-directive:foo 中,參數(shù)為 "foo"。

    modifiers:一個(gè)包含修飾符的對(duì)象。例如:v-my-directive.foo.bar 中,修飾符對(duì)象為 { foo: true, bar: true }

    vnode:Vue 編譯生成的虛擬節(jié)點(diǎn)

    oldVnode:上一個(gè)虛擬節(jié)點(diǎn),僅在 update 和 componentUpdated 鉤子中可用

  3. 除了 el 之外,其它參數(shù)都應(yīng)該是只讀的,切勿進(jìn)行修改。如果需要在鉤子之間共享數(shù)據(jù),建議通過(guò)元素的 dataset 來(lái)進(jìn)行

    <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>
    
    

指令使用的幾種方式

//會(huì)實(shí)例化一個(gè)指令,但這個(gè)指令沒(méi)有參數(shù)
`v-xxx`// -- 將值傳到指令中
`v-xxx="value"`// -- 將字符串傳入到指令中,如`v-html="'<p>內(nèi)容</p>'"`
`v-xxx="'string'"`// -- 傳參數(shù)(`arg`),如`v-bind:class="className"`
`v-xxx:arg="value"`// -- 使用修飾符(`modifier`)
`v-xxx:arg.modifier="value"`;

應(yīng)用場(chǎng)景

輸入框防抖


// 1.設(shè)置v-throttle自定義指令
Vue.directive('throttle', {
  bind: (el, binding) => {
    let throttleTime = binding.value; // 防抖時(shí)間
    if (!throttleTime) { // 用戶(hù)若不設(shè)置防抖時(shí)間,則默認(rèn)2s
      throttleTime = 2000;
    }
    let cbFun;
    el.addEventListener('click', event => {
      if (!cbFun) { // 第一次執(zhí)行
        cbFun = setTimeout(() => {
          cbFun = null;
        }, throttleTime);
      } else {
        event && event.stopImmediatePropagation();
      }
    }, true);
  },
});
// 2.為button標(biāo)簽設(shè)置v-throttle自定義指令
<button @click="sayHello" v-throttle>提交</button>


圖片懶加載

const LazyLoad = {
  // install方法
  install(Vue, options) {
    // 代替圖片的loading圖
    let defaultSrc = options.default;
    Vue.directive("lazy", {
      bind(el, binding) {
        LazyLoad.init(el, binding.value, defaultSrc);
      },
      inserted(el) {
        // 兼容處理
        if ("InterpObserver" in window) {
          LazyLoad.observe(el);
        } else {
          LazyLoad.listenerScroll(el);
        }
      },
    });
  },
  // 初始化
  init(el, val, def) {
    // src 儲(chǔ)存真實(shí)src
    el.setAttribute("src", val);
    // 設(shè)置src為loading圖
    el.setAttribute("src", def);
  },
  // 利用InterpObserver監(jiān)聽(tīng)el
  observe(el) {
    let io = new InterpObserver((entries) => {
      let realSrc = el.dataset.src;
      if (entries[0].isIntersecting) {
        if (realSrc) {
          el.src = realSrc;
          el.removeAttribute("src");
        }
      }
    });
    io.observe(el);
  },
  // 監(jiān)聽(tīng)scroll事件
  listenerScroll(el) {
    let handler = LazyLoad.throttle(LazyLoad.load, 300);
    LazyLoad.load(el);
    window.addEventListener("scroll", () => {
      handler(el);
    });
  },
  // 加載真實(shí)圖片
  load(el) {
    let windowHeight = document.documentElement.clientHeight;
    let elTop = el.getBoundingClientRect().top;
    let elBtm = el.getBoundingClientRect().bottom;
    let realSrc = el.dataset.src;
    if (elTop - windowHeight < 0 && elBtm > 0) {
      if (realSrc) {
        el.src = realSrc;
        el.removeAttribute("src");
      }
    }
  },
  // 節(jié)流
  throttle(fn, delay) {
    let timer;
    let prevTime;
    return function (...args) {
      let currTime = Date.now();
      let context = this;
      if (!prevTime) prevTime = currTime;
      clearTimeout(timer);

      if (currTime - prevTime > delay) {
        prevTime = currTime;
        fn.apply(context, args);
        clearTimeout(timer);
        return;
      }

      timer = setTimeout(function () {
        prevTime = Date.now();
        timer = null;
        fn.apply(context, args);
      }, delay);
    };
  },
};
export default LazyLoad;

一鍵 Copy 的功能

import { Message } from "ant-design-vue";

const vCopy = {
  //
  /*
    bind 鉤子函數(shù),第一次綁定時(shí)調(diào)用,可以在這里做初始化設(shè)置
    el: 作用的 dom 對(duì)象
    value: 傳給指令的值,也就是我們要 copy 的值
  */
  bind(el, { value }) {
    el.$value = value; // 用一個(gè)全局屬性來(lái)存?zhèn)鬟M(jìn)來(lái)的值,因?yàn)檫@個(gè)值在別的鉤子函數(shù)里還會(huì)用到
    el.handler = () => {
      if (!el.$value) {
        // 值為空的時(shí)候,給出提示,我這里的提示是用的 ant-design-vue 的提示,你們隨意
        Message.warning("無(wú)復(fù)制內(nèi)容");
        return;
      }
      // 動(dòng)態(tài)創(chuàng)建 textarea 標(biāo)簽
      const textarea = document.createElement("textarea");
      // 將該 textarea 設(shè)為 readonly 防止 iOS 下自動(dòng)喚起鍵盤(pán),同時(shí)將 textarea 移出可視區(qū)域
      textarea.readOnly = "readonly";
      textarea.style.position = "absolute";
      textarea.style.left = "-9999px";
      // 將要 copy 的值賦給 textarea 標(biāo)簽的 value 屬性
      textarea.value = el.$value;
      // 將 textarea 插入到 body 中
      document.body.appendChild(textarea);
      // 選中值并復(fù)制
      textarea.select();
      // textarea.setSelectionRange(0, textarea.value.length);
      const result = document.execCommand("Copy");
      if (result) {
        Message.success("復(fù)制成功");
      }
      document.body.removeChild(textarea);
    };
    // 綁定點(diǎn)擊事件,就是所謂的一鍵 copy 啦
    el.addEventListener("click", el.handler);
  },
  // 當(dāng)傳進(jìn)來(lái)的值更新的時(shí)候觸發(fā)
  componentUpdated(el, { value }) {
    el.$value = value;
  },
  // 指令與元素解綁的時(shí)候,移除事件綁定
  unbind(el) {
    el.removeEventListener("click", el.handler);
  },
};

export default vCopy;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容