element input多个过滤条件_Vue限制input仅能输入正整数或浮点数指令

该指令主要解决了以下问题:

  • v-model同步问题
  • 中文输入法导致input触发了但v-model同步失效
  • 可扩展到其他UI框架下,如element

通常限制一个 input 仅能输入指定字符我们会去监听其input事件,然后通过正则过滤掉非法字符。然而在vue中,仅仅修改input的value并不会同步到 v-model 上。在vue官方文档中有提到 v-model 其实是 v-bindv-on 合起来的语法糖,如下:

<input v-bind:value="value" v-on:input="$emit('input', $event.target.value)">

由此可以看出v-model的更新其实也是基于监听 input 事件,但由于我们直接修啊修改input的value并不会触发input事件,所以我们只须手工去触发一下这个事件即可。按此逻辑可以先写出一个基础版的指令,如下:

Vue.directive('inputInt', {
  bind(el, binding, vnode) {
    let input = vnode.elm;
    input.addEventListener('input', () => {
      let oldValue = input.value;
      let newValue = input.value.replace(/[^d]/g, '');
      // 判断是否需要更新,避免进入死循环
      if(newValue !== oldValue) {
        input.value = newValue
        input.dispatchEvent(new Event('input')) // 通知v-model更新
      }
    })
  }
})

注意,我们通过手工触发 input 事件会再次进入指令,如此就成了死循环,所以此处需要判断是否需要去更新 v-model ,进而确定是不是需要手工去触发事件。 以上代码看上去是ok的,但实际使用时会遇到一个很奇怪的现象:当用中文输入法时,尝试输入中文的字符确实会被过滤掉,但 v-model 并没有同步,再输入数字时又正常了(在element下若输入中文, v-model将永久不会再同步更新)。这个现象暴露出一个很明显的问题,当我们尝试输入中文是,每敲一个字母就会触发一次input事件,而我们期望的是在确认输入的时候才去校验。幸运的是浏览器提供了一组事件去处理这样的情况 compositionstartcompositionend

MDN释义如下: compositionstart 事件触发于一段文字的输入之前(类似于 keydown 事件,但是该事件仅在若干可见字符的输入之前,而这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词)。 当文本段落的组成完成或取消时, compositionend 事件将被触发 (具有特殊字符的触发, 需要一系列键和其他输入, 如语音识别或移动中的字词建议)。

通过测试发现,我们可以通过这组事件去控制是否触发 input 事件,同时也避免了中文输入法导致 v-model 无法同步的情况发生。一个完整的输入整数指令如下:

Vue.directive('inputInt', {
  bind(el, binding, vnode) {
    let input = vnode.elm;
    input.addEventListener('compositionstart', () => {
      vnode.inputLocking = true
    })
    input.addEventListener('compositionend', () => {
      vnode.inputLocking = false
      input.dispatchEvent(new Event('input'))
    })
    input.addEventListener('input', () => {
      if(vnode.inputLocking) {
        return;
      }
      let oldValue = input.value;
      let newValue = input.value.replace(/[^d]/g, '');
      if(newValue) {
        switch (binding.value) {
          case 'zeroBefore':
            break; // 数字随意输,不做处理,如 000013
          case 'zeroCan':
            newValue = Number(newValue).toString(); // 去掉开头0 正整数 + 0
            break;
          default :
            newValue = newValue.replace(/^b(0+)/gi, ''); // (默认)去掉开头0 正整数
        }
      }
      // 判断是否需要更新,避免进入死循环
      if(newValue !== oldValue) {
        input.value = newValue
        input.dispatchEvent(new Event('input')) // 通知v-model更新
      }
    })
  }
})

至此,以上问题都以解决。当我们想要对更多情况做控制时,只需更改value的值即可。

扩展一个两位浮点数指令

Vue.directive('inputFloat', {
  bind(el, binding, vnode) {
    let input = vnode.elm;
    input.addEventListener('compositionstart', () => {
      vnode.inputLocking = true
    })
    input.addEventListener('compositionend', () => {
      vnode.inputLocking = false
      input.dispatchEvent(new Event('input'))
    })
    input.addEventListener('input', () => {
      if(vnode.inputLocking) {
        return;
      }
      let oldValue = input.value;
      let newValue = input.value;
      newValue = newValue.replace(/[^d.]/g, '');
      newValue = newValue.replace(/^./g, '');
      newValue = newValue.replace('.', '$#$').replace(/./g, '').replace('$#$', '.');
      newValue = newValue.replace(/^(-)*(d+).(dd).*$/, '$1$2.$3')
      if(newValue) {
        let arr = newValue.split('.')
        newValue = Number(arr[0]) + (arr[1] === undefined ? '' : '.' + arr[1]) // 去掉开头多余的0
      }
      // 判断是否需要更新,避免进入死循环
      if(newValue !== oldValue) {
        input.value = newValue
        input.dispatchEvent(new Event('input')) // 通知v-model更新
      }
    })
    // input 事件无法处理小数点后面全是零的情况 因为无法确定用户输入的0是否真的应该清除,如3.02。放在blur中去处理
    input.addEventListener('blur', () => {
      let oldValue = input.value;
      let newValue = input.value;
      if(newValue) {
        newValue = Number(newValue).toString()
      }
      // 判断是否需要更新,避免进入死循环
      if(newValue !== oldValue) {
        input.value = newValue
        input.dispatchEvent(new Event('input')) // 通知v-model更新
      }
    })
  }
})

扩展到其他UI框架

其实扩展到其他框架很简单,整个指令对元素的依赖仅仅表现在节点的获取上,在不同的框架下更改获取 input 节点相应的代码即可。如在element下获取节点的代码为:

let input = vnode.elm.children[0];

作者:Guyou 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

前端应该懂得初级Web分析指标​www.jianshu.com
4e336d94fcbb5a41e3ae09ea0ed37995.png
阿里巴巴腾讯前端面试经验,最完整面试真题分享!​www.jianshu.com
282be65df1919a3384d7a5fd6a1033c1.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值