• 一个Vue3数字框区间指令及其衍生的一个知识点


    大家在实际使用过程中一定会有一个这样的场景,就是一个数字输入框,它的取值范围会被限制。假设我们设置这个数字输入框的取值范围为0~10,那么我们应该在原生的数字输入框这样写:

    <input v-model="value" min='0' max='10' step='1' />
    
    • 1

    但是这样其实并不能符合我们的期望,因为数字输入框的值的更改是有两种方式的,一种是通过输入,还有一种是点击数字输入框内部的箭头去加减step的大小来完成数字的更改,但是minmax这两个属性只能控制箭头的方式而不能控制用户输入的方式,所以我们需要针对用户输入的方式去做更改,具体的实现我们可以参考elementPlus的实现。

    实现方式

    要实现对用用户输入值的控制,我们肯定是需要通过input或者change事件来控制event.target.value,所以我首先就想到的是使用指令的方式去实现。大致的思路如下:

    1. 定义一个全局的指令,该指令接受一个存放最小值和最大值的数组,当数字输入框使用该指令时,用户的输入就会收到限制
    2. 在通过修饰符我们可以判断用户希望的是通过input事件触发还是change事件触发,然后给输入框绑定不同的事件同时限制用户输入的值
    3. 通过修饰符判断用户是否希望消除空字符串更改为0

    大致的实现方式就上面的形式,实现并不困难。

    指令的实现

    我们在main.js中定义一个全局指令,如下所示:

    app.directive("limitRangeOfNumber", {
      mounted(el, binding, vnode, prevVnode) {
        let [min, max] = binding.value;
        let modifiersObj = binding.modifiers;
        let eventName = binding.modifiers.input ? "input" : "change";
        el.min = min;
        el.max = max;
        el.addEventListener(eventName, (e) => {
          let currentValue = +e.target.value;
          console.log(currentValue);
          if (currentValue >= max) {
            currentValue = max;
          }
          if (currentValue <= min) {
            currentValue = min;
          }
          e.target.value = currentValue;
        });
      },
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    一个小问题

    上面的代码比较简单这里就不再赘述了,这里要说的一点是,如果是这样设置,假设用户使用如下:

    <input v-model="value" min='0' max='10' step='1' v-limitRangeOfNumber.input="[0, 10]" />
    
    • 1

    此时会出现这个现象:

    在这里插入图片描述

    当我输入大于最大限制的数字例如13时,输入时的值确实更改为10,但是在失焦状态后值又更改为了13,这一点在change事件时更为明显。最开始我设想的是失焦事件的问题,最起码问题的表面现象就是如此,但是失焦事件的值确实是10,很明显问题不在事件上,于是我将目光转向了Vue上。

    问题分析

    我们都知道v-model其实就是一个双向绑定,在Vue2中V-model是input事件和v-bind的一个语法糖,但在Vue3中,如果你对表单元素或者一个组件绑定了一个v-model其实相当于传递了一个modelValue的prop,并绑定了一个update:modelValue事件,虽然方式不一样但是其实和Vue2的区别不是很大。为什么我要分析v-model呢?原因很简单,除了我通过指令更改输入框的值外只有v-model这一个地方更改了元素的值。

    另外还有一点,大家都知道Vue使用的是虚拟dom,Vue内部会将虚拟的Vnode处理后再渲染到页面上,而Vue对虚拟dom是有处理的,一般情况下已经创建的虚拟DOM是会缓存下来供后面操作更新后使用。

    终上所述所以我们可以猜想,我们确实更新了元素了value,但是虚拟dom缓存了元素的初始target,而不是更改后的value,在我们更改了元素的value后触发了diff算法,这个时候Vnode会更新,但是这个时候VNode更新的是之前缓存的值,因为我们是直接更改的DOM元素而不是虚拟dom,所以虚拟dom更新后会将缓存的值更新为dom的value,这也能解释为什么输入13值会变为10然后再变为13

    解决问题
    第一种:去掉v-model

    经过上面的分析我们知道是因为使用v-model的时候给虚拟DOM添加了一个update:modelValue事件,并在虚拟dom更新的时候会触发该事件将元素的value更改为之前的缓存值,所以我们简单粗暴的去掉v-model就能解决问题,但是这种办法治标不治本,并且v-model不可能再使用表单元素的时候不使用,所以这种办法不具有通用性。

    第二种:更新Vnode

    很幸运的的是,Vue在指令的方法中开放了Vnode的操作权限。在用户使用了v-model的情况下我们可以再Vnode的props属性下拿到一个名为onUpdate:modelValue的方法。

    在这里插入图片描述

    只看名字我们就能明白这个方法的意思,也就是更新v-model的方法,我们也能看到这个方法接受一个参数,并将该参数赋值为v-model的值,那么我只需要将处理好的值传给这个方法并调用就可以。

    vnode.props["onUpdate:modelValue"]
            ? vnode.props["onUpdate:modelValue"](currentValue)
            : (e.target.value = currentValue);
    
    • 1
    • 2
    • 3
    完整代码
    app.directive("limitRangeOfNumber", {
      mounted(el, binding, vnode, prevVnode) {
        let [min, max] = binding.value;
        let modifiersObj = binding.modifiers;
        let eventName = binding.modifiers.input ? "input" : "change";
        el.min = min;
        el.max = max;
        el.addEventListener(eventName, (e) => {
          let currentValue = +e.target.value;
          if (currentValue >= max) {
            currentValue = max;
          }
          if (currentValue <= min) {
            currentValue = min;
          }
          vnode.props["onUpdate:modelValue"]
            ? vnode.props["onUpdate:modelValue"](currentValue)
            : (e.target.value = currentValue);
        });
        if (modifiersObj.zero) {
          el.addEventListener("focus", (e) => {
            let currentValue = e.target.value;
            if (currentValue === "0") {
              currentValue = "";
            }
            vnode.props["onUpdate:modelValue"]
            ? vnode.props["onUpdate:modelValue"](currentValue)
            : (e.target.value = currentValue);
          });
          el.addEventListener("blur", (e) => {
            let currentValue = e.target.value;
            if (currentValue === "") {
              currentValue = 0;
            }
            vnode.props["onUpdate:modelValue"]
            ? vnode.props["onUpdate:modelValue"](currentValue)
            : (e.target.value = currentValue);
          });
        }
      },
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    使用方式
    <input v-model="value" min='0' max='10' step='1' v-limitRangeOfNumber.input.zero="[0, 10]" />
    
    • 1

    传给指令的一定是一个由两个数字或者数字字符串组成的数组,按升序排列,我这里懒得写其他处理情况,就不能惯着,就得按标准方式去使用!

    如果传给指令一个input修饰符事件监听会更改为input事件,否则默认为change事件

    如果需要将空字符串更改为0则需要给指令传一个zero修饰符。

  • 相关阅读:
    一个简单的Oracle Redaction实验
    从头开始实现YOLOV3
    【JavaWeb】练习三
    Linux CentOS7 vim临时文件
    L4W2KT作业 Keras教程
    空调开高一度觉得热、开低一度觉得冷的问题原因,DIY外加温控器解决
    C#学习笔记--数据结构、泛型、委托事件等进阶知识点
    11-08 周三 图解机器学习之实现逻辑异或,理解输出层误差和隐藏层误差项和动量因子
    面试必备:一线大厂Redis缓存设计规范与性能优化
    LangChain转换链:让数据处理更精准
  • 原文地址:https://blog.csdn.net/qq_45279180/article/details/127753116