写在前面
相信大家已经看过某些手机号的输入框在输入的时候,手机号是3 4 4格式,即 输入一个手机号时,会隔成 159 8888 3333 这样的输入框。笔者也实现了一个这样的组件,这个组件的特点是: 组件表现上在输入时会自动隔断成 3 4 4 格式,且只能输入数字
实现原理
基础分析
一开始看到这个需求,我一看,输入的时候劫持一下onChange,格式化一下就好了嘛,啪的一下,很快啊,代码就写好了。上来就是一个正则的捕获括号,一个字符串replace函数哈。
value = value.replace(/(\d{3})(\d{4})/, '$1 $2 ') // 15988883333 => 159 8888 3333
但是产品说你这个没用,我说我这个有用。说着就边输入,边说你这个输入的时候格式化的不对。好吧,我大意了,没有考虑仔细 。但是一想,可以更新一下,对不同长度的字符串适用不同的正则。
const getFormatPhone = (phone: string) => {
const purePhone = phone.replace(/\D/g, '');
const { length } = purePhone;
if (length <= 3) {
return purePhone;
} else if (length <= 7) {
return purePhone.replace(/(\d{3})(\d{0,4})/, '$1 $2');
} else {
return purePhone.replace(/(\d{3})(\d{4})/, '$1 $2 ');
}
};
代码跑起来,看起来很完美。但是怎么删除的时候删不动,细想,原来是在删除最后一个空格时,代码又给我自动格式化出了一个空格。我又大意了。
我刚在想怎么解决,又发现,在字符串中间输入或删除的时候,光标总是跑到最后面去了。
不对劲哈,这个需求不讲码德,来骗,来偷袭,我2岁的前端仔。能骗我两次,骗不了第三次。
仔细分析
认真分析这个需求,发现这个是有多种交互场景的,包括,普通输入,普通删除,字符串中间进行输入或删除,在不同位置下黏贴或者删除多个字符串。总的来说包括三个需要思考的。
- 任何输入的字符串都格式为正确的格式。
- 删除的时候如果是删除空格,需要再往前删除一位。
- 计算好光标的位置。
正确的格式化
这个用上述的getFormatPhone函数处理即可。
删除空格多删除一位
这里就涉及到一个概念,删除的位置。这里可以引入一个属性,selectionStart。在mdn中,是这样解释的
返回/ 设置 the beginning index of the selected text. When nothing is selected, this returns the position of the text input cursor (caret) inside of the `` element.(https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLInputElement)
所以可以利用这个属性来获取用户操作后光标所在的位置,从而来获得光标输入前的位置,从而来判断是否需要多删除一位
矫正光标的位置
光标的位置,这个有点难搞哦。用户有不同的操作模式,这里枚举如下:
- 单纯输入
- 单纯删除
- 在字符串中间开始输入
- 在字符串中间开始删除,包括在空格前删除
- 在字符串不同位置复制多个字符
- 在字符串不同位置剪切(即删除)多个字符
在不同的用户操作模式后都应该重新计算,
笔者总结计算公式为 光标最后的位置 = 光标原本的位置 + 纯粹增删的数字数量 - 差异的空格数量
其中差异的空格数量,是由旧的值中的空格数量 - 新的值中的空格数量。值得注意的是,这里空格数量,算的不是全部字符串中的数量,而是截止到某个位置的空格的数量,其中,旧的值应该截止到光标输入前的位置,新的值应该截止到光标输入后的位置。
这个公式理解起来就是,原本光标在N位置,由于输入了多个字符,所以应该向前向后移动,但此时由于空格,需要计算下由于空格导致的光标位置的差异。
关于删除空格时,多删除一位还是直接光标前移
可能有的人认为在删除空格时,应该自动将光标位置前进一格,而不是直接多删除一位。关于这个交互,笔者从自身用户角度出发,会认为一个删除行为就是想删除一个具体的数字,而不是单纯的前进。所以这里的交互差异暂不做具体纠葛,暂以目前的交互为准。
关于通用格式化
有人可能会说,你这能不能更通用一点,例如格式化为444。关于这个,组件想做到是可以的。可以修改一下props,传入一个所需要的格式数组,如[444],然后修改格式化的函数,将其的不同边界条件修改成传入的变量。
网友评论