Vue常用的6大自定义指令

158 阅读3分钟

「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 () {}

});

​

// 使用自定义指令

<span v-xxx:100.stop="200"></span>

「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');

文本处理的两大自定义指令:v-copy / v-emoji

document.execCommand(“Copy”) 将选中文本复制到剪切板「只对input和textarea有效」\ select() 选择文本

v-copy

Vue.directive('copy', {

bind(el, binding) {

// 初始化:点击元素实现文本复制

el.$value = binding.value;

el.$handle = () => {

let text = el.$value,

textarea = null,

copyText = '';

if (!text) {

alert('亲,需要复制的内容不能为空哦~~');

return;

}

// 创建textarea

textarea = document.createElement('textarea');

textarea.value = text;

textarea.readOnly = 'readonly';

// textarea.style.display = 'none'; //=>不能用这个隐藏

textarea.style.cssText = "opacity:0;position:fixed;left:-9999px";

document.body.appendChild(textarea);

​

// 选中textarea的内容且复制

textarea.select();

copyText = document.execCommand('Copy');

if (copyText) {

alert('亲,恭喜您拷贝成功!');

}

document.body.removeChild(textarea);

};

el.addEventListener('click', el.$handle);

},

componentUpdated(el, binding) {

// 组件更新时,同步更新需要复制的内容「可能内容没有更新」

el.$value = binding.value;

},

unbind(el) {

// 指令与元素解绑时「例如卸载组件」,移除事件绑定

el.removeEventListener('click', el.$handle);

}

});

v-emoji

const trigger = (el, type) => {

const ev = document.createEvent('HTMLEvents');

ev.initEvent(type, true, true);

el.dispatchEvent(ev);

};

Vue.directive('emoji', {

bind(el, binding) {

el.$handle = ev => {

let val = el.value,

reg = /[^0-9a-zA-Z]/g;

el.value = val.replace(reg, '');

// 同步v-modal的数据

trigger(el, 'input');

};

el.$handle();

el.addEventListener('keyup', el.$handle);

},

unbind(el) {

el.removeEventListener('keyup', el.$handle);

}

});

​

//使用

<input type="text" v-model="inpText" v-emoji />

必用指令:v-debounce「防抖」和 v-throttle「截流」

防抖(300ms内重新触发,都会重新计时)和节流(把频率降低,300ms一次)


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

bind(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);

},

unbind(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',

}"

>

点我啊!!

</button>

基于 v-permission /[pəˈmɪʃn]/ 指令实现权限的校验及管理


Vue.directive('permission', {

inserted(el, binding) {

// 获取从服务器获取的用户权限列表 Vuex|Storage 「格式:xxx|xxx...」

let permission = localStorage.getItem('permission'),

permissionList = [];

if (!permission) permission = "";

permissionList = permission.split('|');

​

// 获取用户传递的权限校验标识 「格式:xxx|xxx...」

let passText = binding.value,

passTextArr = [];

if (!passText) passText = "";

passTextArr = passText.split('|');

​

// 循环校验是否有权限

let flag = false,

i = 0,

len = passTextArr.length;

for (; i < len; i++) {

if (permissionList.includes(passTextArr[i])) {

flag = true;

break;

}

}

​

// 控制元素显示隐藏

if (!flag) el.parentNode && el.parentNode.removeChild(el);

}

});

滴滴面试题:基于v-LazyLoad实现图片懒加载

<https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver>


const ob_config = {

threshold: [1]

};

const ob = new IntersectionObserver((entries) => {

entries.forEach(item => {

let {

target,

isIntersecting

} = item;

if (!isIntersecting) return;

let imgBox = target.querySelector('img');

if (!imgBox) return;

imgBox.src = target.$src;

imgBox.style.opacity = 1;

ob.unobserve(target);

});

}, ob_config);

​

Vue.directive('lazyload', {

inserted(el, binding) {

let imgBox = el.querySelector('img');

if (!imgBox) return;

imgBox.src = '';

imgBox.style.opacity = 0;

imgBox.style.transition = 'opacity .3s';

el.$src = binding.value;

ob.observe(el);

}

});