如何自定義指令
注冊(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ù)都有以下:
el:指令所綁定的元素,可以用來(lái)直接操作 DOM
-
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 鉤子中可用 -
除了 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;