Wenzi

使用 React 实现 6 个输入框的短信验证码功能

蚊子前端博客
发布于 2024/05/07 09:41
如何使用 React 实现 6 个输入框的短信验证码功能

在移动端中,经常会有下图中的交互方式,供用户输入短信验证码。

短信验证码输入

目前比较主流的两种实现方案:

  1. 每个单独的验证码,占据一个输入框;
  2. 灰色框框仅仅是展示,外层再盖上透明的输入框;

我们分别来说他们的优缺点和实现方式。

1. 每个数字单独占用一个输入框 #

按照真实个数的输入框来实现,输入框的最大长度设置为 1,然后监听所有的输入框,当输入框中有值时,若当前输入框是最后一个,则触发验证码的校验;若不是最后一个,则把光标移动到下一个输入框。

但这里存在着一个严重的问题:有的操作系统(如 iOS 等),可以帮助自动填充验证码,即便我们限制了输入框的长度,也会把所有验证码都填充到第一个输入框中。

我代码的实现:

const App = () => {
  const list = new Array(6).fill(0); // 初始数组,用来循环生成输入框
  const inputRef = useRef<InputRef[]>([]); // 获取所有的输入框的dom实例

  /**
   * 每个输入框中的数据变化时
   * @param {string} value 当前输入框中的数据
   * @param {number} curIndex 当前是第几个输入框
   */
  const handleChange = (value: string, curIndex: number) => {
    if (value) {
      if (curIndex < list.length - 1) {
        // 若当前输入框不是最后一个,则让下一个输入框获取到焦点
        inputRef.current[curIndex + 1].focus();
      } else {
        // 若当前输入框是最后一个,则拼接输入框中所有的验证码,然后进行验证
        let captcha = "";
        inputRef.current.forEach((item) => {
          captcha += item.nativeElement?.value || "";
        });

        onSuccess({ ...data, captcha });
      }
    }
  };

  /**
   * 监听键盘按下的事件,若是删除键,则删除验证码,同时光标回退
   */
  const handleKeyDown = (event: any, curIndex: number) => {
    const BACK_SPACE_CODE = 8; // 回退删除键

    if (event.keyCode !== BACK_SPACE_CODE) {
      // 这里只处理删除操作,若按键不是删除键,则不处理,直接返回
      return;
    }
    const hasValue = inputRef.current[curIndex].nativeElement?.value;
    if (!hasValue && curIndex) {
      // 若有数据,则删除数据,若没有数据,则回退光标
      inputRef.current[curIndex - 1].focus();
    }
  };


  return (
    <div className="input-list">
      {list.map((item) => (
        <Input
          key={item}
          type="tel"
          ref={(input) => {
            if (input && inputRef.current.length < list.length) {
              inputRef.current.push(input);
            }
          }}
          disabled={loading}
          maxLength={1}
          onChange={(value) => handleChange(value, item)}
          onKeyDown={(event) => handleKeyDown(event, item)}
        />
      ))}
    </div>
  );
};

在实际使用中,体验极度不友好,因此就放弃了这种方式。

2. 透明输入框 #

6 个灰色的框框仅用来展示使用,然后在这上面放一个透明的输入框用来进行输入。

但这里依然有一个体验上的问题,就是不能随意地切换光标。比如我中间有个验证码输错了,想只修改该数字,目前是不可以的,只能先删除后面的,然后再重新输入。

相比第一个方案的实现,目前光标无法随意切换的体验,相对来说还能接受这种方案。

具体光标在哪个灰框框中闪烁,则依据已输入验证码的长度。

const App = () => {
  const CAPTCHA_DEFAULT_LENGTH = 6; // 验证码的个数
  const list = new Array(CAPTCHA_DEFAULT_LENGTH).fill(1);
  const inputRef = (useRef < InputRef) | (null > null);

  const [inputActive, setInputActive] = useState(false); // 透明的验证码输入框是否获得焦点

  // 每个输入框中的数据变化时
  const handleChange = (value: string) => {
    setCaptcha(value);

    if (value.length >= CAPTCHA_DEFAULT_LENGTH) {
      // 验证码达到约定长度时,校验验证码
      onSuccess({ ...data, captcha: value });
    }
  };

  return (
    <div className="input-list">
      <div className="input-content">
        {list.map((_, index) => (
          <p
            key={index}
            className={classNames("input", {
              // 当光标在输入框中让当前正在输入的输入框的光标闪烁
              active: index === Math.min(captcha.length, CAPTCHA_DEFAULT_LENGTH - 1) && inputActive,
            })}
          >
            {captcha[index] || ""}
          </p>
        ))}
      </div>
      <Input
        type="tel"
        ref={inputRef}
        maxLength={CAPTCHA_DEFAULT_LENGTH}
        onChange={handleChange}
        onFocus={() => setInputActive(true)}
        onBlur={() => setInputActive(false)}
      />
    </div>
  );
};

3. 总结 #

没有完美的方案,只能在某些方面做一些取舍。

标签:react
阅读(452)
Simple Empty
No data