啧啧啧,项目中使用自定义指令引发的bug!

418 阅读4分钟

1. 前言

今天测试小姐姐给我提了一个bug,我打开一看一脸懵逼:‘??? 这产品也没提这个需求啊’

大致效果:

一个售价区间的input框:只能输入正整数,不能超过8位数,错误的字符要让他输入不了。

换而言之:不能输入中文,不能输入1.1,不能输入特殊字符。

我第一反应本来是想拒绝的,又想了下今天bug不多,功能也比较简单,把这个解决了也没啥~~~

于是,有了这篇文章的诞生(踩坑之旅开始)。

image.png

2. 效果展示

老规矩,先上成品展示

222.gif

3. 先排除type = 'number'

image.png

 <el-input
        v-model="state.formData.num"
        maxlength="10"
        style="width: 200px"
        type="number"
      />

实际使用过程中,发现不符合测试的要求,如下:

  • maxlength不生效

  • 可以输入e

  • 可以输入-

4. 再排除el-input-number组件

image.png

  • 不想用这个组件~~~

  • 组件左右自带+-护法,不想费工夫去掉这两dom

  • 可以输入-和. (虽然失焦后会移除)

5. 使用自定义指令

遇到上面两个问题后,我第一想法是使用个全局的自定义指令。

这样,不仅全局可用,而且使用方法很简单,直接<inputv-model='xxx' v-inputNumber />就行了

5.1 第一版代码

<el-input
        v-model="state.formData.num"
        v-inputNumber="10"
        type="number"
      />


export default {
  mounted(el, binding, vnode) {
   
   el.addEventListener('input', (e) => {
      let { value } = e.target
      let maxwordlength = bidding.value
      let integer = parseInt(value)
      if (!isNaN(integer)) {
        e.target.value = Math.abs(integer)
        if (maxwordlength) {
          e.target.value = e.target.value.slice(0, maxwordlength)
        }
        // 触发input的change事件
        e.target.dispatchEvent(new Event('input'))
      } else {
        e.target.value = ''
        // 触发input的change事件
        e.target.dispatchEvent(new Event('input'))
      }
      // }
    })
  },
};

实现思路

  • 通过addEventListener监听elinput事件实时得到用户输入的数据

  • 通过parseInt自动去除用户输入的.(比如1.1.1得到111)

  • 通过isNaN判断是否包含特殊字符,对数据执行处理

  • 通过dispatchEvent执行input事件把处理后的数据透传给对应input的value

111.gif

如上图,在多次测试后发现了一个bug

输入中文后,虽然会被拦截成功,但是再输入正常的数字后,对应的v-model绑定的值不更新了~~~

此时,已经花了我一个小时的宝贵时间了

在那无语半天后,想了下,可能是中文+英文两种输入情况下,破坏了input事件处理逻辑里的什么东西,所以想着把中文单独来一波处理,于是有了第二版

5.2 第二版代码

export default {
  mounted(el, binding, vnode) {
    el.addEventListener('compositionend', (e) => {
      console.log("🚀 ~ handleCompositionEnd ~ this.composing = false", e)
      let { value } = e.target;
      const regex = /['\u4e00-\u9fa5]/;
      if (regex.test(value)) {
        e.target.value = ''
        // 触发input的change事件
        e.target.dispatchEvent(new Event('input'));
      }
    });

    el.addEventListener('input', (e) => {
      let { value } = e.target;
      let maxwordlength = bidding.value
      const regex = /['\u4e00-\u9fa5]/;
      if (!regex.test(value)) {
        let integer = parseInt(value);
        if (!isNaN(integer)) {
          console.log("🚀 ~ el.addEventListener ~ integer:", integer, maxWordLength)
          e.target.value = Math.abs(integer)
          if (maxWordLength) {
            e.target.value = e.target.value.slice(0, maxWordLength);
          }
          // 触发input的change事件
          e.target.dispatchEvent(new Event('input'));
        } else {
          e.target.value = ''
          // 触发input的change事件
          e.target.dispatchEvent(new Event('input'));
        }
      }
    });
  },
};

实现思路

  • 第一版的基础上,通过compositionend增加了劫持中文输入的逻辑

  • 对中文场景下的输入数据单独进行dispatchEvent

嘿!你猜怎么着,不行!!!~~~ 依旧没达到我想要的效果

此时,差不多花了我快两小时了

到这里,耐心差不多花光了,毕竟这bug不在这次迭代的需求中

但是,不甘心啊... 都写到这里了

image.png

耐下心想,可能问题出在dispatchEvent上,处理后的数据没成功更新到input的value

于是,想到了常规的父子通信的传值方式,而自定义指令的第三个参数是VNode

所以第三版,也就是最终版出来了

5.3 最终版代码

222.gif

export default {
  mounted(el, bidding, vnode) {
    el.addEventListener('input', (e) => {
      let { value } = e.target
      let maxWordLength = bidding.value
      const regex = /['\u4e00-\u9fa5]/
      if (!regex.test(value)) {
        let integer = parseInt(value)
        if (!isNaN(integer)) {
          console.log(
            '🚀 ~ el.addEventListener ~ integer:',
            integer,
            maxWordLength
          )
          e.target.value = Math.abs(integer)
          if (maxWordLength) {
            e.target.value = e.target.value.slice(0, maxWordLength)
          }
          vnode.ctx.emit('update:modelValue', e.target.value)
        } else {
          e.target.value = ''
          vnode.ctx.emit('update:modelValue', e.target.value)
        }
      } else {
        e.target.value = ''
        vnode.ctx.emit('update:modelValue', e.target.value)
      }
    })
  },
}


实现思路

  • 第一版的基础上,通过vnode.ctx.emit取代了dispatchEvent更新数据方式

经过多次测试,效果如预期~~~

此时,基本花了我快一个下午的宝贵时间

6. 完结

好啦,大功告成啦~~~

这个功能属于看着有点简单,但做起来有点费事的那种,小伙伴工作如果遇到类似功能,可以直接CV大法,多出来的时间又可以摸鱼啦~~~

这篇文章我尽力把我的笔记和想法放到这了,希望对小伙伴有帮助。

欢迎转载,但请注明来源。

最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。

image.png