van-field 组件数值类型限制最大值的使用及思考

2020-07-10  本文已影响0人  holidayPenguin

在使用Vant组件 van-field时需要限制数值类型的最大值,尝试了两种方法,最终确定了一种最优方案

版本说明 "vue": "^2.6.8" "vant": "^2.9.0"

因为在组件van-field外面有嵌套了一层作为自定义组件,方便写一些公共的业务逻辑,就是因为嵌套了一层才导致我最初使用的方法有性能问题和不能准确限制最大值的问题

而且使用的是数字,所以设置type='digit' Tips: digit 类型从 2.4.2 版本开始支持

我们先来看第一种方法

事件传递法

取这个名字的原因是因为所有都是通过事件来完成的,并且是连续的事件。

定义自定义组件

c-input

<template>
  <van-field
    label=""
    :value="value"
    type="digit"
    @input="inputHandler"
    />
</template>
<script>
export default {
  name: 'c-input',
  props: {
    value: {
      default: '',
      type: [Number, String],
    },
    max: {
      default: 0,
      type: [Number, String],
    },
  },
  methods: {
    inputHandler (value) {
      value = this.max > 0 ? Math.min(value, this.max || 0) : value
      this.$emit('input', value)
    },
  }
}
</script>

使用自定义组件

<c-input v-model="inputValue" max="50"></c-input>

首先输入1,可以正常显示1;我们再次输入1,这时显示11;我们继续输入1,这时显示50,但是已经能看到我们由111变成50的过程。

我们继续输入1,这时显示501,而且不会变成50,查看vue-devtoolsevents的事件都发送成功,

image.png image.png

只有在输入框失去焦点的时候才能变成50

image.png

分析

想了一下可能是因为自定义组件c-input中的value值始终没有改变,导致van-field中的值不会改变

尝试一

既然没有改变,是否可以使用setTimeout,在开始改变的时候先改变值为其它值,然后再改变成我们期望的值。

    inputHandler (value) {
      value = this.max > 0 ? Math.min(value, this.max || 0) : value
      this.$emit('input', Date.now())
      setTimeout(() => {
        this.$emit('input', value)
      }, 0)
    },

这种做法会导致事件的死循环,发送第一次input事件会触发vant-fieldinput事件,然后继续发送第一次input事件,导致死循环

这时如果修改第一次input事件的值为空字符串,也还是没达到目的。

这里我很想说先发送第一次input事件,再发送第二次input事件,然后就开始死循环,也许是我没有深入研究,结果就是每次触发vant-field组件input事件的值都是第一次input事件的值。有知道了麻烦在评论区回复一下

尝试二

尝试在computed中增加一个model,作为vant-fieldv-model,同时去掉组件上:value数据和input事件监听

    model: {
      get () {
        return this.value
      },
      set (val) {
        this.inputHandler(val)
      },
    },

其实效果和尝试一是一样的

思来想去只能使用最后一个方法,解耦事件(当然代码要多写一点点,也就是一点点而已)

解耦事件法

c-input组件内定一个data数据叫testValue(这个变量在自定义组件外部是看不到的),同时vant-field增加v-model='testValue'并去掉:value属性

还是要继续监听vant-field的input事件,在事件处理函数中将处理好的数据赋值给testValue(这时vant-field的值的变化是肉眼看不到的),还是要继续发送input事件,使c-input组件使用者知道自定义组件内部值是多少。

但是还有个问题,如果组件外部值变化,c-input组件内部不知道值的变化,这时就需要监听value了然后赋值给testValue

  watch: {
    value (newVal) {
      this.testValue = newVal
    },
  },

同时如果创建创建的时候有初始值也需要赋值操作

  created () {
    this.testValue = this.value
  },

运行效果杠杠的,而且当连续按住键盘不放的时候不会出现卡顿的现象。

这个处理逻辑最主要的是把:value="value" 变成了v-model="textValue",把组件值的改变由事件传递解耦,变成了各个组件各自内部的逻辑,减少了相互的依赖。

其实还是那四个字,单一原则

完整代码

<template>
  <van-field
    label=""
    v-model="testValue"
    type="digit"
    @input="inputHandler"
    />
</template>

<script>
export default {
  name: 'c-input',
  props: {
    value: {
      default: '',
      type: [Number, String],
    },
    max: {
      default: 0,
      type: [Number, String],
    },
  },
  data () {
    return {
      testValue: '',
    }
  },
  watch: {
    value (newVal) {
      this.testValue = newVal
    },
  },
  created () {
    this.testValue = this.value
  },
  methods: {
    inputHandler (value) {
      value = this.max > 0 ? Math.min(value, this.max || 0) : value
      this.testValue = value
      this.$emit('input', value)
    },
  },
}
</script>

看到这里有人可能会问最后发送了input事件,必然会触发valuewatch,导致给textValue赋值,最后还是会触发vant-fieldinput事件,导致死循环。

其实是不会的,触发valuewatch后赋值的valuetextValue是相同的,不会触发vant-fieldinput事件导致死循环。

上一篇下一篇

猜你喜欢

热点阅读