官网文档
一个自定义指令由一个蕴含相似组件生命周期钩子的对象来定义。钩子函数会接管到指令所绑定元素作为其参数。
指令的定义对象能够提供几种钩子函数:
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器利用前调用
created(el, binding, vnode, prevVnode) {// 上面会介绍各个参数的细节},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他本人的所有子节点都挂载实现后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他本人的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}}
钩子参数
- el:指令绑定到的元素。这能够用于间接操作 DOM。
-
binding:一个对象,蕴含以下属性。
- value:传递给指令的值。例如在 v-my-directive=”1 + 1″ 中,值是 2。
- oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。
- arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 “foo”。
- modifiers:一个蕴含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 {foo: true, bar: true}。
- instance:应用该指令的组件实例。
- dir:指令的定义对象。
- vnode:代表绑定元素的底层 VNode。
- prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。
思路
咱们能够通过 arg 传参管制过滤文本或者数字,通过 binding 中传参管制过滤逻辑。
过滤类型
export enum EDirectiveType {
文本 = 'text',
数字 = 'number',
}
文本过滤参数
/**
* @param allowChinese 是否容许中文
* @param allowSpace 是否容许空格
* @param regExp 正则
*/
export interface IDirectiveTextBindingVO {
allowChinese: boolean;
allowSpace: boolean;
regExp: RegExp;
}
数字过滤参数
/**
* @param integral 整数位
* @param decimal 小数位
* @param negative 是否反对正数
* @param min 最小
* @param max 最大
*/
export interface IDirectiveNumberBindingVO {
integral: number;
decimal: number;
negative: boolean;
min: number | null;
max: number | null;
}
实现
指令注册:
const registerInputFilter = (app: App) => {
app.directive('inputFilter', {created(el, binding, vnode) {el._assign = getModelAssigner(vnode);
const type = binding.arg;
addEventListener(el, 'input', (e) => {if ((e.target as any).composing) return;
let domValue: string = el.value;
switch (type) {
case EDirectiveType. 文本:
domValue = handleTextFilter(domValue, binding);
break;
case EDirectiveType. 数字:
domValue = handleNumberFilter(domValue, binding);
break;
}
el._assign(domValue);
});
},
// beforeMount(el, binding) {},
// mounted 中解决是为了指令加载实现后登程过滤逻辑
mounted(el, binding) {
const type = binding.arg;
let domValue: string = el.value;
switch (type) {
case EDirectiveType. 文本:
domValue = handleTextFilter(domValue, binding);
break;
case EDirectiveType. 数字:
domValue = handleNumberFilter(domValue, binding);
break;
}
el.value = domValue;
},
// beforeUpdate(el, binding, vnode) {// console.log('beforeUpdate---el, binding----------------------', el, binding);
// },
// updated(el, binding) {// console.log('updated---el, binding----------------------', el, binding);
// },
// beforeUnmount(el, binding) {// console.log('beforeUnmount---el, binding----------------------', el, binding);
// },
// unmounted(el, binding) {// console.log('unmounted---el, binding----------------------', el, binding);
// },
deep: false,
});
};
export default registerInputFilter;
文本过滤
/**
* 过滤文本
* @param domValue 文本内容
* @param binding 指令传参
* @returns
*/
function handleTextFilter(domValue: string, binding: DirectiveBinding<IDirectiveTextBindingVO>) {const { regExp, allowSpace} = binding.value;
const characters: string = '';
const defaultStr = String.raw`\`\\;\'\"<>`;
const reg = new RegExp(String.raw`[${defaultStr}${characters}]`, 'g');
domValue = domValue.replace(regExp instanceof RegExp ? regExp : reg, '');
// 过滤空格
if (!allowSpace) {domValue = domValue.replace(/\s+/g, '');
}
return domValue;
}
数字过滤
/**
* 数字过滤
* @param domValue 文本内容
* @param binding 指令传参
* @returns
*/
function handleNumberFilter(domValue: string, binding: DirectiveBinding<IDirectiveNumberBindingVO>) {const { decimal, negative, integral, min, max} = binding.value;
const reg = new RegExp(String.raw`[^0-9${Math.ceil(decimal ?? 0) > 0 ? '.' : ''}${negative ?'-':''}]`, 'g');
console.log('decimal------------------------------------------------------', decimal);
console.log('negative-----------------------------------------------------', negative);
console.log('integral-----------------------------------------------------', integral);
domValue = domValue.replace(reg, '');
let symbol = '';
// 解决符号
if (domValue.substring(0, 1) === '-') {
symbol = '-';
domValue = domValue.substring(1);
}
domValue = domValue.replace(/[^0-9\.]/g, '');
// 解决首位小数点
if (domValue.substring(0, 1) === '.') {domValue = `0${domValue}`;
}
// 禁止头部间断输出 0
if (domValue.length > 1 && domValue.substring(0, 1) === '0' && domValue.substring(1, 2) !== '.') {domValue = domValue.substring(1);
}
// 解决小数点及小数位数
if (domValue.includes('.')) {domValue = deduplicate(domValue, '.');
const temp = domValue.split('.');
domValue = `${temp[0]}.${temp[1]?.substring(0, (Math.ceil(decimal ?? 0) > 0 ? Math.ceil(decimal) : null) as number) ?? ''
}`;
}
// 解决头部多余的 0
if (domValue.length > 1) {domValue = domValue.replace(/^0+(?!\.)/, '');
}
// 限度整数长度
const temp = domValue.split('.');
temp[0] = temp[0].substring(0, Math.ceil(integral ?? 10));
domValue = temp.length === 2 ? `${symbol}${temp[0]}.${temp[1]}` : `${symbol}${temp[0]}`;
// 限度最大最小
if (Object.prototype.toString.call(min) !== '[object Undefined]' &&
Object.prototype.toString.call(min) !== '[object Null]' &&
min &&
domValue !== '' &&
looseToNumber(domValue) < min
) {domValue = min + '';}
if (Object.prototype.toString.call(max) !== '[object Undefined]' &&
Object.prototype.toString.call(max) !== '[object Null]' &&
max &&
domValue !== '' &&
looseToNumber(domValue) > max
) {domValue = max + '';}
console.log('domValue-------------------------------', domValue);
return domValue;
}
工具办法
/*
* @Author: wanzp
* @Date: 2023-04-26 21:15:54
* @LastEditors: wanzp
* @LastEditTime: 2023-05-05 20:22:19
* @Description:
*/
import {VNode} from 'vue';
type AssignerFn = (value: any) => void;
export function onCompositionEnd(e: Event) {
const target = e.target as any;
if (target.composing) {
target.composing = false;
target.dispatchEvent(new Event('input'));
}
}
export const invokeArrayFns = (fns: Function[], arg?: any) => {for (let i = 0; i < fns.length; i++) {fns[i](arg);
}
};
export const getModelAssigner = (vnode: VNode): AssignerFn => {const fn = vnode.props!['onUpdate:modelValue'] || (__COMPAT__ && vnode.props!['onModelCompat:input']);
return Array.isArray(fn) ? (value) => invokeArrayFns(fn, value) : fn;
};
/**
* "123-foo" will be parsed to 123
* This is used for the .number modifier in v-model
*/
export const looseToNumber = (val: any): any => {const n = parseFloat(val);
return isNaN(n) ? val : n;
};
export function addEventListener(el: Element, event: string, handler: EventListener, options?: EventListenerOptions) {el.addEventListener(event, handler, options);
}
export function removeEventListener(
el: Element,
event: string,
handler: EventListener,
options?: EventListenerOptions,
) {el.removeEventListener(event, handler, options);
}
export const deduplicate = (target: string, symbol: string): string => {if (target.includes(symbol)) {const temp = target.split(symbol);
let str = `${temp.shift() ?? ''}${symbol}`;
temp.filter((v) => v).forEach((v) => (str += v));
return str;
}
return target;
};