首先我们来看一下,vue3的指令的写法与vue2有什么不同呢?
「Vue2.0」directive.js
// 注册全局自定义指令
Vue.directive('xxx', {
// 指令第一次绑定到元素时「初始化」……
bind: function (el, binding, vnode, oldVnode) {
// el:绑定指令的DOM元素对象
// binding:数据对象
// + name:不带“v-”的指令名字
// + rawName:带“v-”的指令名字「含值和修饰符」
// + value:指令绑定的值 v-xxx="1+1" -> value:2
// + expression:指令表达式 v-xxx="1+1" -> expression:"1+1"
// + arg:传给指令的参数 v-xxx:n -> arg:"n"
// + modifiers:修饰符对象 v-xxx.stop.finish -> modifiers:{stop:true,finish:true}
// vnode:Vue编译生成的虚拟节点
// oldVnode:上一个虚拟节点
},
// 被绑定元素插入父节点时「父节点不一定非要插入到文档中,一般是插入到DOM中」……
inserted: function () {},
// 所在组件的VNode更新时「指令的值可能发生了改变,也可能没有」……
update: function (el, binding, vnode, oldVnode) {
// binding:数据对象
// + oldArg
// + oldValue
// + ...
},
// 所在组件的VNode及其子VNode全部更新后……
componentUpdated: function () {},
// 指令与元素解绑时……
unbind: function () {}
});
「Vue3.0」directive.js
export default function directive(app) {
app.directive('xxx', {
// 指令首次绑定到元素且在安装父组件之前...「等同于bind」
beforeMount(el, binding, vnode, prevVnode) {
// binding:数据对象
// + arg:传给指令的参数 v-xxx:n -> arg:"n"
// + modifiers:修饰符对象 v-xxx.stop -> modifiers:{stop:true}
// + value:指令绑定的值 v-xxx="1+1" -> value:2
// + oldValue:之前绑定的值
},
// 安装绑定元素的父组件时...「等同于inserted」
mounted() {},
// 在包含组件的VNode更新之前...
beforeUpdate() {},
// 在包含组件的VNode及其子VNode更新后...「等同于componentUpdated」
updated() {},
// 在卸载绑定元素的父组件之前...
beforeUnmount() {},
// 指令与元素解除绑定且父组件已卸载时...「等同于unbind」
unmounted() {}
});
};
// main.js
import {
createApp
} from 'vue';
import App from './App.vue';
import directive from './directive';
const app = createApp(App);
directive(app);
app.mount('#app');
可以看出,vue3指令的钩子和vue2有所区别,其他在写法上与vue2没有太大的区别。
防抖和节流相信大家也很熟悉啦,在这里就不多介绍了。我是这么区别防抖和节流的,多次触发事件只执行一次是防抖,按规定的频率执行是节流。
接下来,下面让我们一起来实现v-debounce和v-throttle 吧。
//先定义throttle和debounce方法
//从loadsh里复制出来的
const throttle = function throttle(func, wait) {
if (typeof func !== "function") throw new TypeError('func must be a function!');
wait = +wait;
if (isNaN(wait)) wait = 300;
let timer = null,
previous = 0,
result;
return function proxy(...params) {
let now = +new Date,
remaining = wait - (now - previous),
self = this;
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
previous = now;
result = func.apply(self, params);
return result;
}
if (!timer) {
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
previous = +new Date;
result = func.apply(self, params);
}, remaining);
}
return result;
};
};
const debounce = function debounce(func, wait, immediate) {
if (typeof func !== "function") throw new TypeError('func must be a function!');
if (typeof wait === "undefined") {
wait = 500;
immediate = false;
}
if (typeof wait === "boolean") {
immediate = wait;
wait = 500;
}
if (typeof immediate === "undefined") {
immediate = false;
}
if (typeof wait !== "number") throw new TypeError('wait must be a number!');
if (typeof immediate !== "boolean") throw new TypeError('immediate must be a boolean!');
let timer = null,
result;
return function proxy(...params) {
let self = this,
callNow = !timer && immediate;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
if (!immediate) result = func.apply(self, params);
}, wait);
if (callNow) result = func.apply(self, params);
return result;
};
}
const common_config = {
beforeMount(el, binding) {
let {
func,
wait = 300,
immediate = true,
params = [],
type = 'click'
} = binding.value;
const handle = binding.name === 'debounce' ? debounce : throttle,
proxy = function proxy(...args) {
return func.call(this, ...params.concat(args));
};
el.$type = type;
el.$handle = handle(proxy, wait, immediate);
el.addEventListener(el.$type, el.$handle);
},
unmounted(el) {
el.removeEventListener(el.$type, el.$handle);
}
};
Vue.directive('debounce', common_config);
Vue.directive('throttle', common_config);
//使用
<button
class="submit"
v-throttle="{
func: submitHandle,
wait: 1000,
immediate: true,
params: [10, 20],
type: 'click',
}"
/>