[Element plus]源码学习---Input Number数字输入框

1、结构

首先我们先从结构入手:

在element plus里面就拿一个最普通的input Number框来讲 翻开源码查看结构,我们得知就是一个很标准的一个大盒子里面装着两个span,一个el-input

2、@ dragstart.prevent

在查看结构的时候,从大盒子开始看,bem规范我们撇开不看,就很明显看到@dragstart.prevent @dragstart.prevent:拖拽开始就阻止自身的默认事件(禁止数字拖拽 /禁止图片被拖拽 属于vue自带事件)

3、加减事件

这里我们发现element plus 这里是使用v-repeat-click去触发加减事件的,v-repeat-click是什么呢? v-repeat-click其实就是重复点击,用于函数防抖的

v-repeat-click会注册mousedown事件,当用户连续点击+时: 当用户鼠标左键一直按住不松手,只会触发一次触发mousedown的回调,但实际测量el-input-number发现,输入框中的数字会持续变大,原因就在于mousedown回调中加入了定时器,当鼠标松开,触发一次mouseup回调方法,取消该定时器;这也许是directive为什么叫repeat-click的缘故吧; 如果时间间隔大于100毫秒,那么mousedown的回调方法里的setInterval回调就会执行(及handler,本质上就是执行上图的decrease或increase方法); 如果时间间隔小于100毫秒,定时器就会取消; mousedown的回调方法(clear方法)每次执行时,都会通过once方法注册并执行一次mouseup回调; 在mouseup回调中,如果发现距离最近一次点击时间小于100ms,就会执行一次handler方法,并清除定时器;

js 复制代码
import { isFunction } from '@element-plus/utils'

import type { ObjectDirective } from 'vue'

export const REPEAT_INTERVAL = 100  //时间间隔
export const REPEAT_DELAY = 600  // 延迟时间

// 重复单击选项
export interface RepeatClickOptions {
  interval?: number
  delay?: number
  handler: (...args: unknown[]) => unknown
}

export const vRepeatClick: ObjectDirective<
  HTMLElement,
  RepeatClickOptions | RepeatClickOptions['handler']
> = {
  beforeMount(el, binding) {
    const value = binding.value
    const { interval = REPEAT_INTERVAL, delay = REPEAT_DELAY } = isFunction(
      value
    )
      ? {}
      : value

    let intervalId: ReturnType<typeof setInterval> | undefined
    let delayId: ReturnType<typeof setTimeout> | undefined

    //获取表达式内容
    const handler = () => (isFunction(value) ? value() : value.handler())
    // clear 函数  --- 清理定时器  
    const clear = () => {
      if (delayId) {
        clearTimeout(delayId)
        delayId = undefined 
      }
      if (intervalId) {
        clearInterval(intervalId)
        intervalId = undefined
      }
    }
    // 注册事件监听,绑定鼠标按下事件
    el.addEventListener('mousedown', (evt: MouseEvent) => {
      if (evt.button !== 0) return // evt.button表示一个数值代表鼠标按键
      clear()
      handler()
      
     // 监听鼠标抬起事件,但只监听一次
      document.addEventListener('mouseup', () => clear(), {
        once: true,
      })

      delayId = setTimeout(() => {
        intervalId = setInterval(() => {
          handler()
        }, interval)
      }, delay)
    })
  },
}

以上代码,小弟我有两个问题想问问各位同学 Q1:为什么监听的主体是document而不是像上面使用el呢? Q2:为什么监听鼠标抬起事件需要用once,而不像上面用直接on呢?

A1:因为在点击过程中,鼠标可能会移除当前el那个容器外面再进行抬起动作,所以需要绑定document而不是el A2:因为使用on的话会一直监听,而el容器外点击鼠标也会调用到到clear()

补充一些上述提及'evt.button'的鼠标按键数值(鼠标事件):

当初查阅资料的时候,其实还有3:浏览器后退;4:浏览器前进,但不常用这里一笔带过。

js 复制代码
// '加号'方法
const increase = () => {
// 判断是否 为 只读状态、inputNumber是否禁用状态、是否达到最大禁用值(max属性计算)
  if (props.readonly || inputNumberDisabled.value || maxDisabled.value) return
  const value = props.modelValue || 0
  const newVal = ensurePrecision(value)
  setCurrentValue(newVal)
  emit(INPUT_EVENT, data.currentValue)
}
// '减号'方法
const decrease = () => {
// 判断是否 为 只读状态、inputNumber是否禁用状态、是否达到最小禁用值(min属性计算)
  if (props.readonly || inputNumberDisabled.value || minDisabled.value) return
  // modelValue 为v-model绑定的变量
  const value = props.modelValue || 0
  // ensurePrecision() 是通过将值转换为整数,解决JS的精度问题的一个函数来的
  const newVal = ensurePrecision(value, -1)
  // 设置当前值
  setCurrentValue(newVal)
  emit(INPUT_EVENT, data.currentValue)
}

是否达到最大禁用值(max属性计算)

4、精度

一个前端常识判断题:0.1+0.2 = 0.3 是否正确? 答案是错误的

如果这样的话,我们0.1+0.2这样的精度问题出现了。 那在Input Number里面是如何解决精度问题(将小数转换成整数计算,再除以位数)呢? 之前在学习的时候我看到别人文章中是这样写到:element的解决思路是将值扩大精度倍进行计算,得到结果后再除以精度倍数。 那我们来看一下源码

js 复制代码
const increase = () => {
// 判断是否只读/禁用状态的值/最大限制的值
  if (props.readonly || inputNumberDisabled.value || maxDisabled.value) return
  // 拿到value 然后赋值给newVal 然后调用ensurePrecision()
  const value = props.modelValue || 0
  const newVal = ensurePrecision(value)
  // 以下只作数值更新
  setCurrentValue(newVal)
  emit(INPUT_EVENT, data.currentValue)
}

const ensurePrecision = (val: number, coefficient: 1 | -1 = 1) => {
  if (!isNumber(val)) return data.currentValue
  // 通过将值转换为整数来解决JS小数计算的准确性问题
  return toPrecision(val + props.step * coefficient)
}
// 对数值进行加工 确保误差情况会被消除
const toPrecision = (num: number, pre?: number) => {
  if (isUndefined(pre)) pre = numPrecision.value
  if (pre === 0) return Math.round(num)
  let snum = String(num)
  const pointPos = snum.indexOf('.')
  if (pointPos === -1) return num
  const nums = snum.replace('.', '').split('')
  const datum = nums[pointPos + pre]
  if (!datum) return num
  const length = snum.length
  if (snum.charAt(length - 1) === '5') {
    snum = `${snum.slice(0, Math.max(0, length - 1))}6`
  }
  //最后 再调用toFixed()函数展示精度
  return Number.parseFloat(Number(snum).toFixed(pre))
}

5、严格步进

在element Plus讲述过了step-strictly的使用方法 我们再去看看API,这里提示是否输入的step的倍数

js 复制代码
// currentValue :缓存上次输入的值
// userInput :缓存当前输入框的值
//监听器 监听model绑定的value 进行更新
watch(
  () => props.modelValue,
  (value) => {
  // 调用verifyValue()方法
    data.currentValue = verifyValue(value, true)
    data.userInput = null
  },
  { immediate: true }
)
const verifyValue = (
  value: number | string | null | undefined,
  update?: boolean
): number | null | undefined => {
// 在这里把stepStrictly拿出来
  const { max, min, step, precision, stepStrictly, valueOnClear } = props
  let newVal = Number(value)
  if (isNil(value) || Number.isNaN(newVal)) {
    return null
  }
  if (value === '') {
    if (valueOnClear === null) {
      return null
    }
    newVal = isString(valueOnClear) ? { min, max }[valueOnClear] : valueOnClear
  }
  // 判断是否有这个标识
  if (stepStrictly) {
    newVal = toPrecision(Math.round(newVal / step) * step, precision)
  }
  if (!isUndefined(precision)) {
    newVal = toPrecision(newVal, precision)
  }
  if (newVal > max || newVal < min) {
    newVal = newVal > max ? max : min
    update && emit(UPDATE_MODEL_EVENT, newVal)
  }
  return newVal
}
相关推荐
q***38513 小时前
SpringBoot + vue 管理系统
vue.js·spring boot·后端
喵个咪4 小时前
go-kratos-admin 快速上手指南:从环境搭建到启动服务(Windows/macOS/Linux 通用)
vue.js·go
用户841794814564 小时前
vxe-gantt table 甘特图如何设置任务视图每一行的背景色
vue.js
小章鱼学前端4 小时前
2025 年最新 Fabric.js 实战:一个完整可上线的图片选区标注组件(含全部源码).
前端·vue.js
涔溪4 小时前
实现将 Vue3 项目作为子应用,通过无界(Wujie)微前端框架接入到 Vue2 主应用中(Vue2 为主应用,Vue3 为子应用)
vue.js·前端框架·wujie
源码技术栈7 小时前
什么是云门诊系统、云诊所系统?
java·vue.js·spring boot·源码·门诊·云门诊
lcc1877 小时前
Vue3 ref函数和reactive函数
前端·vue.js
艾小码7 小时前
还在为组件通信头疼?defineExpose让你彻底告别传值烦恼
前端·javascript·vue.js
带只拖鞋去流浪7 小时前
迎接2026,重新认识Vue CLI (v5.x)
前端·vue.js·webpack
Coder-coco7 小时前
游戏助手|游戏攻略|基于SprinBoot+vue的游戏攻略系统小程序(源码+数据库+文档)
java·vue.js·spring boot·游戏·小程序·论文·游戏助手