前言
上一篇文章中, 通过状态合并,计算input元素最终使用的value值, 来完成组件的受控与非受控模式的支持,今天这篇文章中主要实现控制value值的最大输入长度。主要会实现的关键Props有以下几个:
showWordLimit: 配合maxLength, 显示字数统计。maxLength: 输入框最大输入的长度; 设置errorOnly为 true后, 超过maxLength会展示error状态, 并不限制用户输入。normalize:在指定得时机对用户输入的值进行格式化处理。前后值不一致时,会触发onChange事件。normlizeTrigger: 指定normalize执行的时机。 ('onBlur' | 'onPressEnter' )[]onPressEnter: 按下回车键时的回调。
字数限制:
maxLength:
实现思路:
实际上就是获取Input组件中value值的长度,然后解析maxLength的值,将该值作为input元素的属性值传递,限制输入的value的长度。
不过还需要考虑到errorOnly的情形, 当声明errorOnly时,超过maxLength会展示错误的状态。于是我们派生出两个数据,其中一个maxLength负责绑定input元素中,限制输入框的大小。另外一个realMaxLength用来和value的值进行比较。
代码:
// props派生数据
const maxLength = isObject(propMaxLength) // 绑定到input元素上的maxLength的值
? propMaxLength?.errorOnly
? undefined
: propMaxLength.length
: propMaxLength
const realMaxLength = isObject(propMaxLength) ? propMaxLength.length : propMaxLength // 计算是否超出长度的值
坑:
当遇见输入内容是组合而成的情况时,比如中文,就是通过拼音输入。这一过程中如果发生字数超出时,组件内部就会根据输入过程中的字数显示, 有可能会出现error的状态。就像下面截图内容一样。
这种情况下,我们就需要监听Input组件中原有的composition事件,composition事件是专门用来处理组合输入法。
解决思路:
- 当
Input组件处于输入状态时,设置一个组合输入的临时值供Input组件展示时使用,此时不改变真正的value。 - 处理用户的组合输入事件, 并在输入完成之后清空临时值,触发
value更新。 - 为了防止在组合输入的过程中,
onChange被频繁的触发,因此要代理一下onChange事件,限制值改变的时机。
function useComposition({
value,
onChange
}: {
value: string,
onChange: InputProps['onChange']
}) {
const isCompositionRef = useRef<boolean>(false)
const [compositionValue, setCompositionValue] = useState<string>()
const triggerChange: InputProps["onChange"] = (newValue, event) => {
if (
onChange
&& value !== newValue
// &&
) {
onChange(newValue, event)
}
}
const handleComposition = (event: CompositionEvent<HTMLInputElement>) => {
// composition结束时设定ref
isCompositionRef.current = event.type !== "compositionend"
if (!isCompositionRef.current) {
setCompositionValue(undefined)
// 处理composition输入结束后的value改变。
triggerChange((event.target as HTMLInputElement).value, event)
}
}
const valueChangeHandler = (event: CompositionEvent<HTMLInputElement> | ChangeEvent<HTMLInputElement>) => {
const newValue = (event.target as HTMLInputElement).value
if (!isCompositionRef.current) {
compositionValue && setCompositionValue(undefined)
// 处理英文直接输入值改变
triggerChange(newValue, event)
} else {
isCompositionRef.current = false
setCompositionValue(newValue)
}
}
return {
compositionValue,
handleComposition,
valueChangeHandler
}
}
测试效果
showWordLimit:
实现思路:
Input组件实际上是由多个元素组件组合而来的,因此通过顶替props中传递的suffix元素,来展示字数统计内容。其中的展示内容可以获取value的值, 计算出value的长度,结合上一步中获取到的realMaxLength的值。
代码:
// 派生数据
let suffix = propSuffix
if (showWordLimit) {
const valueLength = value?.length
suffix = (
<span className=''>
{`${valueLength} / ${realMaxLength}`}
</span>
)
}
normalize & normalizeTrigger
实现思路:
将normalize作为props中参数传入, 然后在合适的时机(normalizeTrigger)时调用, 在调用的过程中要检查normalize的合法性。
代码:
// 获取normalize
const normalizeTriggerHandler = (type: "onPressEnter" | "onBlur") => {
const triggers = normalizeTrigger || ['onBlur']
if(
isArray(triggers)
&& triggers.indexOf(type) > -1
&& isFunction(normalize)
)
return normalize
}
// onBlur时
<input
...
onBlur={(event: FocusEvent<HTMLInputElement>) => {
props?.onBlur?.(event)
const normalizeHandler = normalizeTriggerHandler("onBlur")
normalizeHandler && triggerChange(normalizeHandler(value), event)
}}
...
/>
onPressEnter
实现思路:
通过在useComposition的钩子中拦截keydown事件,当不在组合输入的事件中时,并且当key的类型为enter时, 调用onPressEnter的函数, 最后在onPressEnter中条件对normalize时机的支持。
代码:
export function useComposition({
value,
onChange,
onPressEnter,
onKeyDown,
normalizeTriggerHandler
}: {
value: string,
onChange: InputProps['onChange'],
onPressEnter: InputProps['onPressEnter'],
onKeyDown: InputProps['onKeyDown'],
normalizeTriggerHandler: (type: "onPressEnter" | "onBlur") => InputProps['normalize']
}) {
const isCompositionRef = useRef<boolean>(false)
...
const keydownHandler = (event: KeyboardEvent<HTMLInputElement>) => {
if (isCompositionRef.current)
return
console.log(event)
onKeyDown?.(event)
if(event.key === "Enter") {
onPressEnter?.(event)
const normalizeHandler = normalizeTriggerHandler('onPressEnter')
normalizeHandler && triggerChange(normalizeHandler(value), event)
}
}
return {
...,
keydownHandler
}
}
测试效果:
- 按下回车键后, 所有的输入转成小写内容。
import { Input } from "@mini-ui/ui";
export default function app () {
return (
<div style={{display: "flex", flexDirection: "column", gap: "20px"}}>
<Input
normalize={value => value.toLocaleLowerCase()}
normalizeTrigger={['onBlur']}
/>
<Input
normalize={value => value.toLocaleLowerCase()}
normalizeTrigger={['onPressEnter']}
onPressEnter={e => console.log("press enter")}
/>
</div>
)
}
总结:
本篇文章中,主要聚焦在如何实现Input组件输入最大长度限制,解决了组合输入会导致字数显示的问题。并且实现了触发格式化的时机以及格式化的props属性。
下次文章中,会聚焦在如何实现组合前缀后缀后Input组件,并完成对应样式。