导航
导航
文章目录
  1. 需求
  2. 尝试
    1. 总体思路
    2. 第一次尝试
    3. 第二次尝试
  3. 优化
    1. 判断是否中文输入
  4. 总结

Input组件字数限制引发的问题

前言:最近在完善 Input 组件字数计算的时候引发了不少的问题:1、到限制字数时,继续输入中文会替换末尾的文字;2、不同浏览器截断的表现不一样等等问题。

先明确一下需求

需求

  • 输入到最大长度时,超出截断
  • 首尾非文字(空格、换行等)不计入字数统计中
  • 超出截断时保持已输入的文字以及开头非文字部分

尝试

总体思路

1、不能直接使用 input 的 maxLength,因为计算数字的时候会把首尾非文字也计算其中

2、监听 input 的 onChange 事件,当输入的长度大于最大长度时截断

根据以上思路,既然不能直接使用 input 的 maxLength,那就在 onChange 事件里进行逻辑处理。

第一次尝试

1
2
3
4
5
6
7
const v = e.target.value.trim();
if (v.length > maxLength && !("value" in props)) {
setNum(maxLength);
inputRef.current.value = v.slice(0, maxLength);
return;
}
setNum(v.length);

存在的问题

  • 输入到最大长度时开头空格或换行会被截掉
  • 输入中文时会从尾部开始替换掉输入框的值
  • 尾部空格较多时,截取的时候会出问题,计算数字的时候会把空格计算其中

第二次尝试

1
2
3
4
5
6
7
8
9
10
11
12
const { value } = e.target;
const v = value.trim();
if (value.length - v.length !== 0) {
setLength(maxLength + (value.length - v.length)); // 动态计算maxLength保证首尾空格或换行还在
}
if (v.length > maxLength) {
const newValue = v.slice(0, -(v.length - maxLength)).trim(); // 防止倒数第一个前面的值为空格
inputRef.current.value = value.slice(0, -(v.length - maxLength));
setNum(newValue.length);
} else {
setNum(v.length);
}

通过上面两个逻辑判断和处理已经解决了第一次尝试的两个问题,那还有一个问题是不是没解决呢?其实在其他浏览器能解决输入中文时替换掉输入框的值,但是 Chrome 浏览器却不行,WTF!Chrome 竟然不行。其实原因是我上面提到的 Chrome 和其他浏览器输入中文时的表现不一样。

我们用两张图来看一下就明白了

Chrome

其他浏览器

第一张图是 Chrome 输入中文的表现,第二张图是其他浏览器输入中文的表现。是的,Chrome 输入中文时会实时把拼音显示在输入框内,这样就会导致在截取的时候判断不准确替换掉了尾部的值,那有没有什么解决方案呢?那肯定是有的,接下来就来优化一下这个问题。

优化

判断是否中文输入

当用户使用拼音输入法输入时,我们会发现 onChange/onInput 取得的值是拼音值,但是很明显,我们需要计算的是用户输入的中文值的长度,而不是拼音值的长度。所以这里需要解决使用拼音输入法时会取得拼音值的问题。

我们先来看两个比较陌生的事件:

  • compositionstart:文本合成系统如 input method editor(即输入法编辑器)开始新的输入合成时会触发 compositionstart 事件。例如,当用户使用拼音输入法开始输入汉字或者使用语音输入时,这个事件就会被触发。
  • compositionend:当文本段落的组成完成或取消时, compositionend 事件将被触发 (具有特殊字符的触发,需要一系列键和其他输入,如语音识别或移动中的字词建议)。例如,当用户使用拼音输入法输入汉字或者使用语音输入完毕或者取消时,这个事件就会被触发。

因此我们可以声明一个标记lock,在compositionstartcompositionend两个事件过程之间的时候lock值为 true,在 input onChange事件中通过lock的值来判断当前输入的状态和截取。

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
if (e.type === 'compositionstart') {
lock.current = true;
}
if (e.type === "compositionend") {
lock.current = false;
}
const { value } = e.target;
const v = value.trim();
if (value.length - v.length !== 0) {
setLength(maxLength + (value.length - v.length))
}
if (v.length > maxLength) {
const newValue = v.slice(0, -(v.length - maxLength)).trim();
if (!lock.current) {
inputRef.current.value = value.slice(0, -(v.length - maxLength));
}
setNum(newValue.length);
} else {
setNum(v.length);
}

// html
<input
ref={inputRef}
className="yp-input__inner"
onChange={handleChange}
onCompositionStart={handleChange}
onCompositionEnd={handleChange}
...
/>

总结

那到这里一个 Input 组件字数计算的问题总算解决了,从中也是吸取了不少的经验和总结,继续加油!!!