• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

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

武飞扬头像
仙鱼阿
帮助17

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方法,并清除定时器;

学新通

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:浏览器前进,但不常用这里一笔带过。

// '加号'方法
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的解决思路是将值扩大精度倍进行计算,得到结果后再除以精度倍数。 那我们来看一下源码

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的倍数

学新通

// 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
}

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfffjge
系列文章
更多 icon
同类精品
更多 icon
继续加载