ElementUI2.0源码解析系列之el-input-number组件

DOM结构

html 复制代码
//省略版
<div class="el-input-number">
  <span class="el-input-number__decrease">
    <i class="el-icon-minus"></i>
  </span>
  <span class="el-input-number__increase">
    <i class="el-icon-plus"></i>
  </span>
  <div class="el-input-number__wrapper">
    <el-input></el-input>
    <span class="el-input-number__unit"></span>
  </div>
</div>

组件的根元素是一个div,具有el-input-number类名。在这个div中,有两个子元素span,分别表示减少按钮和增加按钮。每个按钮中都包含一个i元素,用于显示减号和加号的图标。紧接着是一个div,具有el-input-number__wrapper类名,用于包裹输入框和单位元素。

html 复制代码
<div class="el-input-number__wrapper">
  <el-input
    ref="input"
    :value="displayValue"
    :placeholder="placeholder"
    :disabled="inputNumberDisabled"
    :size="inputNumberSize"
    :max="max"
    :min="min"
    :name="name"
    :label="label"
    @keydown.up.native.prevent="increase"
    @keydown.down.native.prevent="decrease"
    @blur="handleBlur"
    @focus="handleFocus"
    @input="handleInput"
    @change="handleInputChange">
  </el-input>
  <span class="el-input-number__unit" ref="unit" v-if="controlsAtRight && unit">{{ unit }}</span>
</div>

在这个包裹元素中,有一个el-input组件,用于显示和输入数字。它具有一些属性,如value、placeholder、disabled、size、max、min、name、label等,以及一些事件监听器,如keydown.up.native.prevent、keydown.down.native.prevent、blur、focus、input、change等。最后,如果controlsAtRight为true且unit存在,则会在包裹元素中添加一个span元素,具有el-input-number__unit类名,用于显示单位。

mounted、updated

javascript 复制代码
mounted() {
  let innerInput = this.$refs.input.$refs.input;
  innerInput.setAttribute('role', 'spinbutton');
  innerInput.setAttribute('aria-valuemax', this.max);
  innerInput.setAttribute('aria-valuemin', this.min);
  innerInput.setAttribute('aria-valuenow', this.currentValue);
  innerInput.setAttribute('aria-disabled', this.inputNumberDisabled);
  if (this.controlsAtRight && this.unit) {
    this.styleObj = { paddingRight: this.$refs.unit.clientWidth + this.$refs.button.clientWidth + 'px' };
  } else if (this.controlsAtRight && !this.unit) {
    this.styleObj = { paddingRight: 10 + this.$refs.button.clientWidth + 'px' };
  } else {
    this.styleObj = {};
  }
},
updated() {
  if (!this.$refs || !this.$refs.input) return;
  const innerInput = this.$refs.input.$refs.input;
  innerInput.setAttribute('aria-valuenow', this.currentValue);
}

组件的mounted和updated中对输入框进行了一些属性和样式的设置。 值得注意得是aria-前缀的属性做什么的呢? aria 英文全称:Accessible Rich Internet Application ,翻译成中文就是:可访问的富互联网应用程序。其实它是一组属性,定义了使残疾人更容易访问 web 内容和 web 应用程序(尤其是使用JavaScript开发的应用程序)的方法。

computed

组件的computed中定义了一些计算属性,如minDisabled表示是否禁用减少按钮,maxDisabled表示是否禁用增加按钮,numPrecision表示数字的精度,controlsAtRight表示是否将增减按钮放在右侧,inputNumberSize表示输入框的尺寸,inputNumberDisabled表示输入框是否禁用,displayValue表示显示的值。

watch

  • 组件的watch中监听了value和controlsAtRight的变化,当value变化时,会根据一些条件对新值进行处理,并将处理后的值赋给currentValue,然后触发input事件。
  • 同时也监听了unit的变化,在代码中可以知道,当只有同时设置了controlsAtRight属性的时候,才会对styleObj进行样式的更改。另外还监听了styleObj属性,当styleObj变化时会动态设置input元素的样式。

data

javascript 复制代码
data() {
  return {
    currentValue: 0,
    userInput: null,
    styleObj: {}
  };
}

在组件的data中,定义了一些初始值,如currentValue表示当前的值,userInput表示用户输入的值,styleObj表示样式对象。

props

  • step: 表示每次增加或减少的步长,默认为1。
  • stepStrictly: 表示是否严格按照步长进行增减操作,默认为false。
  • max: 表示允许的最大值,默认为Infinity。
  • min: 表示允许的最小值,默认为-Infinity。
  • value: 表示当前的值,可以是任意类型。
  • disabled: 表示是否禁用输入框和按钮,默认为false。
  • size: 表示输入框和按钮的尺寸,可以是字符串类型。
  • unit: 表示输入框后面的单位文本,可以是字符串类型。
  • controls: 表示是否显示增加和减少按钮,默认为true。
  • controlsPosition: 表示增加和减少按钮的位置,默认为空字符串。
  • name: 表示输入框的名称,可以是字符串类型。
  • label: 表示输入框的标签文本,可以是字符串类型。
  • placeholder: 表示输入框的占位符文本,可以是字符串类型。
  • precision: 表示数字的精度,即小数点后的位数,必须是非负整数。
  • 这些属性用于配置组件的行为和样式,父组件可以通过这些属性向子组件传递数据,并根据需要进行相应的操作。

methods

组件的methods中定义了一些方法,如toPrecision用于将数字保留指定的精度,getPrecision用于获取数字的精度,_increase和_decrease用于增加和减少数字,increase和decrease用于处理增加和减少按钮的点击事件,handleBlur和handleFocus用于处理输入框的失焦和聚焦事件,setCurrentValue用于设置当前的值,handleInput和handleInputChange用于处理输入框的输入事件,select用于选中输入框的内容。

  • toPrecision(num, precision)
javascript 复制代码
toPrecision(num, precision) {
    if (precision === undefined) precision = this.numPrecision;
    return parseFloat(Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision));
}

在组件中,它被用于处理数字的精度,例如在计算增加或减少步长时,以及在显示值时进行精度处理。该方法接受两个参数:num表示要处理的数字,precision表示要保留的小数位数。如果precision参数未定义,则使用组件中的numPrecision属性作为默认精度。在方法内部,首先使用Math.pow函数计算出一个精度因子,即10的precision次方。然后将num乘以精度因子,再使用Math.round函数对结果进行四舍五入。最后,将结果除以精度因子,并使用parseFloat函数将其转换为浮点数类型

  • getPrecision(value)
javascript 复制代码
getPrecision(value) {
    if (value === undefined) return 0;
    const valueString = value.toString();
    const dotPosition = valueString.indexOf('.');
    let precision = 0;
    if (dotPosition !== -1) {
      precision = valueString.length - dotPosition - 1;
    }
    return precision;
}

这个函数的作用是根据给定的数字,判断其小数位数的精度。在组件中,它被用于计算步长和当前值的小数位数精度,以及在显示值时进行精度处理。在函数内部,首先进行了判断,如果value参数为undefined,则直接返回0,表示没有小数位数。接下来,将value转换为字符串类型,并使用indexOf方法查找小数点的位置。如果找到了小数点(即dotPosition不等于-1),则通过计算字符串长度减去小数点位置减1,得到小数位数的精度。

  • _increase(val, step)
javascript 复制代码
_increase(val, step) {
    if (typeof val !== 'number' && val !== undefined) return this.currentValue;
    const precisionFactor = Math.pow(10, this.numPrecision);
    // Solve the accuracy problem of JS decimal calculation by converting the value to integer.
    return this.toPrecision((precisionFactor * val + precisionFactor * step) / precisionFactor);
}

在组件中,它被用于处理增加按钮的点击事件,根据步长和精度对当前值进行增加操作。在函数内部,首先进行了类型检查,如果val不是数字类型且不是undefined,则直接返回当前的值this.currentValue。接下来,定义了一个精度因子precisionFactor,它是通过将10的this.numPrecision次方计算得到的。然后,通过将val乘以精度因子,再加上step乘以精度因子,得到一个中间结果。这一步是为了解决JavaScript中小数计算的精度问题,通过将值转换为整数进行计算。最后,将中间结果除以精度因子,并使用this.toPrecision方法对结果进行精度处理,得到最终的增加后的值。

  • _decrease(val, step)
javascript 复制代码
_decrease(val, step) {
    if (typeof val !== 'number' && val !== undefined) return this.currentValue;
    const precisionFactor = Math.pow(10, this.numPrecision);
    return this.toPrecision((precisionFactor * val - precisionFactor * step) / precisionFactor);
}

这个函数与_increase()函数大同小异,唯一不同的是是个增加函数、一个是减少函数。这个则是减少函数。

  • increase
javascript 复制代码
increase() {
    if (this.inputNumberDisabled || this.maxDisabled) return;
    const value = this.value || 0;
    const newVal = this._increase(value, this.step);
    this.setCurrentValue(newVal);
}

在组件中,它被用于处理增加按钮的点击事件,根据步长和当前值进行增加操作,并更新显示的值。在函数内部,首先进行了条件判断,如果inputNumberDisabled为true(表示输入框被禁用)或者maxDisabled为true(表示已达到最大值),则直接返回,不进行增加操作。接下来,获取当前的值value,如果值为undefined,则将其设置为0。然后,调用_increase方法,将当前值value和步长step作为参数传递进去,得到一个新的值newVal。最后,调用setCurrentValue方法,将新的值newVal作为参数传递进去,用于更新当前的值。

  • decrease
javascript 复制代码
decrease() {
    if (this.inputNumberDisabled || this.minDisabled) return;
    const value = this.value || 0;
    const newVal = this._decrease(value, this.step);
    this.setCurrentValue(newVal);
}

这个函数与increase()函数大同小异,唯一不同的是是个点击增加事件、一个是点击减少事件。这个则是点击减少事件。

  • setCurrentValue(value)
javascript 复制代码
setCurrentValue(newVal) {
    const oldVal = this.currentValue;
    if (typeof newVal === 'number' && this.precision !== undefined) {
      newVal = this.toPrecision(newVal, this.precision);
    }
    if (newVal >= this.max) newVal = this.max;
    if (newVal <= this.min) newVal = this.min;
    if (oldVal === newVal) return;
    this.userInput = null;
    this.$emit('input', newVal);
    this.$emit('change', newVal, oldVal);
    this.currentValue = newVal;
}

在组件中,它被用于处理用户输入、更新当前值,并触发input和change事件。在函数内部,首先保存旧的值oldVal。然后,进行判断,如果newVal的类型是数字,并且this.precision不为undefined,则调用toPrecision方法,将newVal按照指定的精度进行处理。接下来,进行一系列的判断和处理。如果newVal大于等于最大值this.max,则将newVal设置为最大值。如果newVal小于等于最小值this.min,则将newVal设置为最小值。然后,再次进行判断,如果旧的值oldVal等于新的值newVal,则直接返回,不进行后续操作。接下来,将this.userInput设置为null,表示用户输入为空。然后,通过$emit方法触发input事件,将新的值newVal作为参数传递出去。接着,再次通过$emit方法触发change事件,将新的值newVal和旧的值oldVal作为参数传递出去。最后,将当前的值this.currentValue设置为新的值newVal。

  • handleInputChange(value)
javascript 复制代码
handleInputChange(value) {
    const newVal = value === '' ? undefined : Number(value);
    if (!isNaN(newVal) || value === '') {
      this.setCurrentValue(newVal);
    }
    this.userInput = null;
}

它被用于监听输入框的input事件,处理用户输入的值,并更新当前的值。首先根据输入的值value进行判断,如果值为空字符串'',则将newVal设置为undefined,否则将newVal设置为将输入的值转换为数字类型。接下来,进行一系列的判断。如果newVal不是NaN(即输入的值是一个有效的数字)或者值为空字符串'',则调用setCurrentValue方法,将newVal作为参数传递进去,用于更新当前的值。然后,将this.userInput设置为null,表示用户输入为空。

v-repeat-click

javascript 复制代码
import { once, on } from 'element-ui/src/utils/dom';

export default {
  bind(el, binding, vnode) {
    let interval = null;
    let startTime;
    const handler = () => vnode.context[binding.expression].apply();
    const clear = () => {
      if (Date.now() - startTime < 100) {
        handler();
      }
      clearInterval(interval);
      interval = null;
    };

    on(el, 'mousedown', (e) => {
      if (e.button !== 0) return;
      startTime = Date.now();
      once(document, 'mouseup', clear);
      clearInterval(interval);
      interval = setInterval(handler, 100);
    });
  }
};

在这段代码中,首先定义了一些变量,如interval用于存储定时器的ID,startTime用于记录鼠标按下的时间。然后定义了handler函数,它会调用指令绑定的表达式对应的方法。接下来定义了clear函数,它用于清除定时器,并根据鼠标按下的时间判断是否立即执行一次绑定的方法。在mousedown事件的回调函数中,首先判断鼠标按下的按钮是否为左键,如果不是则直接返回。然后记录鼠标按下的时间为startTime,并使用once函数绑定了mouseup事件,当鼠标松开时会执行clear函数。然后清除之前的定时器,再使用setInterval函数每隔100毫秒执行一次handler函数。

源代码

input-number官方源码

相关推荐
Bigger4 天前
这个需求妹子不会!哎,又要帮妹子做需求了......
前端·vue.js·element
那你能帮帮我吗6 天前
element-ui的el-color-picker颜色选择器组件,弹窗定位在左上角的问题排查和解决
前端·element
GDAL1 个月前
element-plus教程:Input Number 数字输入框
element
GDAL1 个月前
element-plus教程:Input 输入框
element
GDAL1 个月前
element-plus教程:Layout 布局
element
努力挣钱的小鑫1 个月前
【Element】vue2 el-table scope.row 更改数据,试图没有更新
前端·javascript·vue.js·element
上海_彭彭2 个月前
【提效工具开发】Python功能模块执行和 SQL 执行 需求整理
开发语言·python·sql·测试工具·element
October_CanYang2 个月前
elementUI中el-tree 展开收起(折叠)和 父节点半选状态初始化回显并传给接口
前端·vue.js·element
我看刑2 个月前
el-datepicker禁用未来日期(包含时分秒)type=‘datetime’
前端·vue·element
October_CanYang2 个月前
vue+ElementUI实现下拉分级菜单:el-select嵌套el-tree树形控件实现下拉树效果(附带模糊查询搜索功能)
vue.js·element