Input篇-字数限制&输入格式化

431 阅读4分钟

前言

上一篇文章中, 通过状态合并,计算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事件是专门用来处理组合输入法。

解决思路:
  1. Input组件处于输入状态时,设置一个组合输入的临时值供Input组件展示时使用,此时不改变真正的value
  2. 处理用户的组合输入事件, 并在输入完成之后清空临时值,触发value更新。
  3. 为了防止在组合输入的过程中,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组件,并完成对应样式。

仓库地址