惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
L
LINUX DO - 热门话题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
雷峰网
雷峰网
aimingoo的专栏
aimingoo的专栏
博客园_首页
MongoDB | Blog
MongoDB | Blog
V
V2EX
GbyAI
GbyAI
量子位
Microsoft Azure Blog
Microsoft Azure Blog
有赞技术团队
有赞技术团队
G
Google Developers Blog
云风的 BLOG
云风的 BLOG
B
Blog
Microsoft Security Blog
Microsoft Security Blog
S
SegmentFault 最新的问题
O
OpenAI News
N
News and Events Feed by Topic
博客园 - Franky
爱范儿
爱范儿
Forbes - Security
Forbes - Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V2EX - 技术
V2EX - 技术
Application and Cybersecurity Blog
Application and Cybersecurity Blog
N
News and Events Feed by Topic
N
News | PayPal Newsroom
Schneier on Security
Schneier on Security
Cloudbric
Cloudbric
Security Archives - TechRepublic
Security Archives - TechRepublic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Recent Commits to openclaw:main
Recent Commits to openclaw:main
人人都是产品经理
人人都是产品经理
P
Privacy International News Feed
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog RSS Feed
阮一峰的网络日志
阮一峰的网络日志
D
DataBreaches.Net
Last Week in AI
Last Week in AI
罗磊的独立博客
Spread Privacy
Spread Privacy
Recent Announcements
Recent Announcements
The Cloudflare Blog
Google DeepMind News
Google DeepMind News
AWS News Blog
AWS News Blog
The Register - Security
The Register - Security
Y
Y Combinator Blog
J
Java Code Geeks
I
Intezer

博客园 - 冯叶青

OpenAI vs Anthropic API 对比:响应体、请求体、消息格式与工具调用 GitHub Actions 自动部署流程 Git分支自动合并脚本:基于时间戳的冲突解决方案 Next.js lingui.js 多语言自动提取翻译键 - ats-node Next.js 中优雅地使用 Lottie 动画 Next.js 路由参数更新最佳实践:从 replaceState 到 nextReplaceState Jenkins构建时SWR模块导入报错解决方案 解决 Jenkins 环境下 Lingui 构建报错 "btoa is not defined" git 提交 实现大图自动压缩功能 React lingui.js 多语言自动提取翻译键 - ast node html2canvas 解决截图空白问题 react 实现前端发版监测 JS根据文件名获取文件类型 JS获取本机IP地址 适用于react、vue菜单格式化工具函数 git 内容提交 实现大图拦截功能 JS实现视频截图 next.js 利用中间件(middleware.ts)实现PC与移动路由无缝切换 Android生成签名文件及对apk进行签名 JS-SDK 配置,实现微信分享功能
React实现短信验证码输入组件
冯叶青 · 2025-03-10 · via 博客园 - 冯叶青

在日常开发中,短信验证码输入是一个非常常见的需求。今天和大家分享一个基于React实现的验证码输入组件,它具有以下特点:

  • 支持6位数字验证码输入
  • 自动聚焦下一个输入框
  • 支持回退删除
  • 支持移动端输入
  • 输入完成后自动触发回调

组件代码实现

const SmsVerify = ({
  onComplete,
  onChange
}: {
  onComplete?: (code: string) => void
  onChange?: (value: string) => void
}) => {
  const [code, setCode, getCode] = useGetState<string[]>([])
  const inputRefs = useRef<(HTMLInputElement | null)[]>([])

  useEffect(() => {
    const seat = new Array(6).fill('')
    setCode(seat)
  }, [])

  useEffect(() => {
    // 当所有格子都填满时触发完成回调
    if (code.every((v) => v !== '') && onComplete) {
      onComplete(code.join(''))
    }
    onChange?.(code.join(''))
  }, [code, onComplete])

  const handleOnChange = ({
    value,
    index
  }: {
    value: string | number
    index: number
  }) => {
    // 将value转换为字符串并确保只保留数字
    const stringValue = String(value).replace(/[^0-9]/g, '')
    // 只取第一个数字
    const singleDigit = stringValue.slice(0, 1)

    if (singleDigit) {
      setCode(
        produce((draft) => {
          draft[index] = singleDigit
        })
      )
      // 自动聚焦下一个输入框
      if (index < 5) {
        inputRefs.current[index + 1]?.focus()
      }
    }
  }

  const handleKeyDown = (
    e: React.KeyboardEvent<HTMLInputElement>,
    index: number
  ) => {
    if (e.key === 'Backspace') {
      // 当前格子为空时,删除键将焦点移到上一个输入框
      setCode(
        produce((draft) => {
          draft[index] = ''
        })
      )
      inputRefs.current[index - 1]?.focus()
      e.preventDefault()
    }
  }

  return (
    <div className="sms-verify">
      <div className="flex gap-[0.1rem]">
        {code.map((value, index) => {
          return (
            <InputNumber
              className={classNames('text-[0.4rem]', { 'has-content': value })}
              ref={(el) => {
                if (el) {
                  inputRefs.current[index] = el
                }
              }}
              type="tel"
              value={value}
              key={'T-' + index}
              controls={false}
              onKeyDown={(e) => handleKeyDown(e, index)}
              onInput={(value) =>
                handleOnChange({ value: value || '', index })
              }
            />
          )
        })}
      </div>
    </div>
  )
}

关键点

  • 使用 type="tel" 调起数字键盘,如果把type类型换成number,在移动端会有兼容性问题,用户可以输入特殊字符和中文,用type="tel",则不会有这个情况
  • onInput 代替onChange事件,如果用onChange,最后一个输入框可以无限制输入内容,删除内容时,无法删除输入的全部内容,而使用onInput 时,能一次性删除当前输入框所有内容