官网文档

一个自定义指令由一个蕴含相似组件生命周期钩子的对象来定义。钩子函数会接管到指令所绑定元素作为其参数。

指令的定义对象能够提供几种钩子函数:

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;};