React中实现一次性密码/验证码框

342 阅读1分钟

前言

最近开发忘记密码功能,需要通过邮箱或者短信接收验证码,然后通过一次性输入框的形式来输入验证码,如下图。然后antd中一次性密码框,在5.16.0版本是有该组件的,但是现有的项目的antd在该版本之前,所以只能自己手搓一个了:

image.png

代码实现

index.tsx

/**
 *
 * 一次性密码/验证码输入框
 */
import React, { useState, useRef, useEffect } from 'react';
import './index.less';

interface OtpInputProps {
  length: number;
  isNumber?: boolean;
  onChange?: (otp: string) => void;
  onComplete: (otp: string) => void;
}

const OtpInput: React.FC<OtpInputProps> = (props) => {
  const { length, isNumber = false, onChange, onComplete } = props;
  const [otp, setOtp] = useState<string[]>(Array(length).fill(''));
  const inputsRef = useRef<HTMLInputElement[]>([]);

  const handleChange = (value: string, index: number) => {
    // 数字输入
    if (isNumber && !/^\d*$/.test(value)) return;

    const newOtp = [...otp];
    newOtp[index] = value.slice(-1);
    setOtp(newOtp);
    if (onChange) {
      onChange(newOtp.join(''));
    }

    // 如果当前输入不为空,则将焦点移至下一个输入字段
    if (value && index < length - 1) {
      inputsRef.current[index + 1].focus();
    }

    // 如果OTP完成,触发onComplete
    if (newOtp.join('').length === length) {
      // 输入完成
      onComplete(newOtp.join(''));
    }
  };

  const handleKeyDown = (e: React.KeyboardEvent, index: number) => {
    if (e.key === 'Backspace' && !otp[index] && index > 0) {
      inputsRef.current[index - 1].focus();
    }
  };

  useEffect(() => {
    inputsRef.current[0]?.focus();
  }, []);

  return (
    <div className={'my-otp'}>
      {Array(length)
        .fill('')
        .map((_, index) => (
          <input
            key={index}
            type="text"
            maxLength={1}
            value={otp[index]}
            onChange={(e) => handleChange(e.target.value, index)}
            onKeyDown={(e) => handleKeyDown(e, index)}
            ref={(el) => (inputsRef.current[index] = el!)}
            className={'my-otp-input'}
          />
        ))}
    </div>
  );
};

export default OtpInput;

index.less

.my-otp {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  &-input {
    width: 36px;
    height: 36px;
    border-radius: 8px 8px 8px 8px;
    text-align: center;
    border: solid 1px rgba(#0b2470, 0.1);
    &:focus {
      border: solid 1px rgba(#0b2470, 0.1);
      outline: none;
      box-shadow: 0 2px 4px 0 rgba(11, 36, 112, 0.1);
    }
  }
}

总结

至此就通过代码实现了一次性密码/验证码输入框。