一波附、 什么是自定義指令
我們看到的v-開頭的行內屬性诈火,都是指令,不同的指令可以完成或實現不同的功能灵妨,對普通 DOM元素進行底層操作解阅,這時候就會用到自定義指令落竹。除了核心功能默認內置的指令 (v-model 和 v-show)泌霍,Vue 也允許注冊自定義指令
指令使用的幾種方式:
//會實例化一個指令,但這個指令沒有參數
`v-xxx`
// -- 將值傳到指令中
`v-xxx="value"`
// -- 將字符串傳入到指令中述召,如`v-html="'<p>內容</p>'"`
`v-xxx="'string'"`
// -- 傳參數(`arg`)朱转,如`v-bind:class="className"`
`v-xxx:arg="value"`
// -- 使用修飾符(`modifier`)
`v-xxx:arg.modifier="value"`
二、 如何自定義指令
注冊一個自定義指令有全局注冊與局部注冊
全局注冊注冊主要是用過Vue.directive
方法進行注冊
Vue.directive
第一個參數是指令的名字(不需要寫上v-前綴)积暖,第二個參數可以是對象數據藤为,也可以是一個指令函數
// 注冊一個全局自定義指令 `v-focus`
Vue.directive('focus', {
// 當被綁定的元素插入到 DOM 中時……
inserted: function (el) {
// 聚焦元素
el.focus() // 頁面加載完成之后自動讓輸入框獲取到焦點的小功能
}
})
局部注冊通過在組件options
選項中設置directive
屬性
directives: {
focus: {
// 指令的定義
inserted: function (el) {
el.focus() // 頁面加載完成之后自動讓輸入框獲取到焦點的小功能
}
}
}
然后你可以在模板中任何元素上使用新的v-focus
property,如下:
<input v-focus />
鉤子函數
自定義指令也像組件那樣存在鉤子函數:
bind
:只調用一次夺刑,指令第一次綁定到元素時調用缅疟。在這里可以進行一次性的初始化設置inserted
:被綁定元素插入父節(jié)點時調用 (僅保證父節(jié)點存在,但不一定已被插入文檔中)update
:所在組件的 VNode 更新時調用遍愿,但是可能發(fā)生在其子 VNode更新之前存淫。指令的值可能發(fā)生了改變,也可能沒有沼填。但是你可以通過比較更新前后的值來忽略不必要的模板更新componentUpdated
:指令所在組件的 VNode 及其子 VNode 全部更新后調用unbind
:只調用一次桅咆,指令與元素解綁時調用
所有的鉤子函數的參數都有以下:
el
:指令所綁定的元素,可以用來直接操作 DOMbinding
:一個對象坞笙,包含以下 property:
`name`:指令名岩饼,不包括 v- 前綴荚虚。
`value`:指令的綁定值,例如:v-my-directive="1 + 1" 中籍茧,綁定值為 2版述。
`oldValue`:指令綁定的前一個值,僅在 update 和 componentUpdated 鉤子中可用寞冯。無論值是否改變都可用院水。
`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 編譯生成的虛擬節(jié)點
`oldVnode`:上一個虛擬節(jié)點,僅在 update 和 componentUpdated 鉤子中可用
除了 el 之外牵署,其它參數都應該是只讀的漏隐,切勿進行修改。如果需要在鉤子之間共享數據奴迅,建議通過元素的 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>
三青责、應用場景
使用自定義組件組件可以滿足我們日常一些場景,這里給出幾個自定義組件的案例:
防抖
圖片懶加載
一鍵 Copy的功能
輸入框防抖
防抖這種情況設置一個v-throttle自定義指令來實現
舉個例子:
// 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>
圖片懶加載
設置一個v-lazy自定義組件完成圖片懶加載
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 鉤子函數脖隶,第一次綁定時調用,可以在這里做初始化設置
el: 作用的 dom 對象
value: 傳給指令的值暇检,也就是我們要 copy 的值
*/
bind(el, { value }) {
el.$value = value; // 用一個全局屬性來存?zhèn)鬟M來的值产阱,因為這個值在別的鉤子函數里還會用到
el.handler = () => {
if (!el.$value) {
// 值為空的時候,給出提示块仆,我這里的提示是用的 ant-design-vue 的提示构蹬,你們隨意
Message.warning('無復制內容');
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;
拖拽
<div ref="a" id="bg" v-drag></div>
directives: {
drag: {
bind() {},
inserted(el) {
el.onmousedown = (e) => {
let x = e.clientX - el.offsetLeft;
let y = e.clientY - el.offsetTop;
document.onmousemove = (e) => {
let xx = e.clientX - x + "px";
let yy = e.clientY - y + "px";
el.style.left = xx;
el.style.top = yy;
};
el.onmouseup = (e) => {
document.onmousemove = null;
};
};
},
},
},