官网文档
一个自定义指令由一个蕴含相似组件生命周期钩子的对象来定义。钩子函数会接管到指令所绑定元素作为其参数。
指令的定义对象能够提供几种钩子函数:
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;};