如何自定義指令
注冊一個自定義指令有全局注冊與局部注冊
全局注冊注冊主要是用過 Vue.directive 方法進行注冊
Vue.directive 第一個參數(shù)是指令的名字(不需要寫上 v-前綴),第二個參數(shù)可以是對象數(shù)據(jù),也可以是一個指令函數(shù)
// 注冊一個全局自定義指令 `v-focus`
Vue.directive("focus", {
// 當被綁定的元素插入到 DOM 中時……
inserted: function (el) {
// 聚焦元素
el.focus(); // 頁面加載完成之后自動讓輸入框獲取到焦點的小功能
},
});
局部注冊通過在組件options選項中設置directive屬性
directives: {
focus: {
// 指令的定義
inserted: function (el) {
el.focus() // 頁面加載完成之后自動讓輸入框獲取到焦點的小功能
}
}
}
鉤子函數(shù)
自定義指令也像組件那樣存在鉤子函數(shù):
- bind:只調(diào)用一次,指令第一次綁定到元素時調(diào)用。在這里可以進行一次性的初始化設置
- inserted:被綁定元素插入父節(jié)點時調(diào)用 (僅保證父節(jié)點存在,但不一定已被插入文檔中)
- update:所在組件的 VNode 更新時調(diào)用,但是可能發(fā)生在其子 VNode更新之前。指令的值可能發(fā)生了改變,也可能沒有。但是你可以通過比較更新前后的值來忽略不必要的模板更新
- componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用
- unbind:只調(diào)用一次,指令與元素解綁時調(diào)用
所有的鉤子函數(shù)的參數(shù)都有以下:
el:指令所綁定的元素,可以用來直接操作 DOM
-
binding:一個對象,包含以下 property:
name:指令名,不包括 v- 前綴。value:指令的綁定值,例如:v-my-directive="1 + 1" 中,綁定值為 2。oldValue:指令綁定的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。expression:字符串形式的指令表達式。例如 v-my-directive="1 + 1" 中,表達式為 "1 + 1"。arg:傳給指令的參數(shù),可選。例如 v-my-directive:foo 中,參數(shù)為 "foo"。modifiers:一個包含修飾符的對象。例如:v-my-directive.foo.bar 中,修飾符對象為 { foo: true, bar: true }vnode:Vue 編譯生成的虛擬節(jié)點oldVnode:上一個虛擬節(jié)點,僅在 update 和 componentUpdated 鉤子中可用 -
除了 el 之外,其它參數(shù)都應該是只讀的,切勿進行修改。如果需要在鉤子之間共享數(shù)據(jù),建議通過元素的 dataset 來進行
<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>
指令使用的幾種方式
//會實例化一個指令,但這個指令沒有參數(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"`;
應用場景
輸入框防抖
// 1.設置v-throttle自定義指令
Vue.directive('throttle', {
bind: (el, binding) => {
let throttleTime = binding.value; // 防抖時間
if (!throttleTime) { // 用戶若不設置防抖時間,則默認2s
throttleTime = 2000;
}
let cbFun;
el.addEventListener('click', event => {
if (!cbFun) { // 第一次執(zhí)行
cbFun = setTimeout(() => {
cbFun = null;
}, throttleTime);
} else {
event && event.stopImmediatePropagation();
}
}, true);
},
});
// 2.為button標簽設置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 儲存真實src
el.setAttribute("src", val);
// 設置src為loading圖
el.setAttribute("src", def);
},
// 利用InterpObserver監(jiān)聽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)聽scroll事件
listenerScroll(el) {
let handler = LazyLoad.throttle(LazyLoad.load, 300);
LazyLoad.load(el);
window.addEventListener("scroll", () => {
handler(el);
});
},
// 加載真實圖片
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ù),第一次綁定時調(diào)用,可以在這里做初始化設置
el: 作用的 dom 對象
value: 傳給指令的值,也就是我們要 copy 的值
*/
bind(el, { value }) {
el.$value = value; // 用一個全局屬性來存?zhèn)鬟M來的值,因為這個值在別的鉤子函數(shù)里還會用到
el.handler = () => {
if (!el.$value) {
// 值為空的時候,給出提示,我這里的提示是用的 ant-design-vue 的提示,你們隨意
Message.warning("無復制內(nèi)容");
return;
}
// 動態(tài)創(chuàng)建 textarea 標簽
const textarea = document.createElement("textarea");
// 將該 textarea 設為 readonly 防止 iOS 下自動喚起鍵盤,同時將 textarea 移出可視區(qū)域
textarea.readOnly = "readonly";
textarea.style.position = "absolute";
textarea.style.left = "-9999px";
// 將要 copy 的值賦給 textarea 標簽的 value 屬性
textarea.value = el.$value;
// 將 textarea 插入到 body 中
document.body.appendChild(textarea);
// 選中值并復制
textarea.select();
// textarea.setSelectionRange(0, textarea.value.length);
const result = document.execCommand("Copy");
if (result) {
Message.success("復制成功");
}
document.body.removeChild(textarea);
};
// 綁定點擊事件,就是所謂的一鍵 copy 啦
el.addEventListener("click", el.handler);
},
// 當傳進來的值更新的時候觸發(fā)
componentUpdated(el, { value }) {
el.$value = value;
},
// 指令與元素解綁的時候,移除事件綁定
unbind(el) {
el.removeEventListener("click", el.handler);
},
};
export default vCopy;