50天50个小项目 (React19 + Tailwindcss V4) ✨| VerifyAccountUi(验证码组件)
使用React19 和 TailwindCSS构建验证码输入组件的实践。该项目是50个小项目挑战的一部分,主要实现6位验证码输入功能,包含自动聚焦、键盘导航和输入验证等特性。
📅 今天很晚才来写,我们继续 50 个小项目挑战!——VerifyAccountUi组件
仓库地址:https://gitee.com/hhm-hhm/50days50projects.git

创建一个简洁而优雅的验证码输入界面。通过这个示例,你将学会如何构建一个响应式且用户友好的验证码输入组件,并掌握一些实用的 Vue 技巧和 Tailwind CSS 样式技巧。
准备好了吗?让我们开始吧!🚀
🌀 组件目标
- 动态生成6个独立的验证码输入框
- 实现自动聚焦与跳转到下一个/上一个输入框的功能
- 添加适当的视觉反馈以提升用户体验
- 采用 Tailwind CSS 快速构建美观的响应式布局
🔧 VerifyAccountUi.tsx组件实现
import React, { useState, useEffect, useRef } from 'react'
const VerifyAccountUi: React.FC = () => {
// 初始化 6 位验证码数组
const [codes, setCodes] = useState<string[]>(new Array(6).fill(''))
// 创建 refs 数组,用于管理每个 input 元素
const codeInputs = useRef<(HTMLInputElement | null)[]>([])
// 组件挂载后聚焦第一个输入框
useEffect(() => {
if (codeInputs.current[0]) {
codeInputs.current[0].focus()
}
}, [])
// 处理键盘事件
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, index: number) => {
const key = e.key
// 只允许数字、Backspace 和 Tab(Tab 用于正常跳转)
if (!/^\d$/.test(key) && key !== 'Backspace' && key !== 'Tab') {
e.preventDefault()
return
}
// 数字输入
if (/^\d$/.test(key)) {
const newCodes = [...codes]
newCodes[index] = key
setCodes(newCodes)
// 自动聚焦下一个输入框
if (index < 5 && codeInputs.current[index + 1]) {
setTimeout(() => codeInputs.current[index + 1]?.focus(), 10)
}
}
// 退格键处理
if (key === 'Backspace') {
const newCodes = [...codes]
newCodes[index] = ''
setCodes(newCodes)
// 聚焦上一个输入框(如果当前为空或刚被清空)
if (index > 0 && codeInputs.current[index - 1]) {
setTimeout(() => codeInputs.current[index - 1]?.focus(), 10)
}
e.preventDefault() // 阻止默认删除行为(避免重复触发)
}
}
// 同步更新输入框值(受控组件)
const handleChange = (e: React.ChangeEvent<HTMLInputElement>, index: number) => {
const value = e.target.value
// 只保留最后一位数字(防止粘贴多字符)
if (/^\d?$/.test(value)) {
const newCodes = [...codes]
newCodes[index] = value.slice(-1)
setCodes(newCodes)
}
}
return (
<div className="flex min-h-screen items-center justify-center overflow-hidden bg-gray-900 font-sans">
<div className="max-w-2xl rounded-lg border-4 border-black bg-white p-8 text-center">
<h2 className="mb-2 text-2xl font-bold">Verify Your Account</h2>
<p className="mb-8 text-gray-600">
We emailed you the six digit code to cool_guy@email.com
<br />
Enter the code below to confirm your email address.
</p>
{/* 验证码输入框容器 */}
<div className="mb-8 flex flex-wrap items-center justify-center">
{codes.map((_, index) => (
<input
key={index}
ref={(el) => {
codeInputs.current[index] = el
}}
value={codes[index]}
onChange={(e) => handleChange(e, index)}
onKeyDown={(e) => handleKeyDown(e, index)}
className="mx-1 my-2 h-20 w-20 rounded-md border border-gray-200 text-center text-5xl font-light [caret-color:transparent] transition-all duration-300 focus:border-blue-500 focus:shadow-lg focus:shadow-blue-200/50 focus:outline-none"
type="text"
maxLength={1}
inputMode="numeric"
pattern="[0-9]"
aria-label={`Digit ${index + 1}`}
/>
))}
</div>
<small className="inline-block max-w-md rounded-md bg-gray-100 px-4 py-2 text-sm text-gray-600">
This is design only. We didn't actually send you an email as we don't have your
email, right?
</small>
</div>
<div className="fixed right-20 bottom-5 z-100 text-2xl text-red-500">
CSDN@Hao_Harrision
</div>
</div>
)
}
export default VerifyAccountUi
🔄 关键差异总结
| 功能 | Vue 3 | React + TS |
|---|---|---|
| 响应式数组 | ref(['', '', ...]) |
useState<string[]>(...) |
| 多元素引用 | ref([]) + v-for ref |
useRef + 回调 ref |
| 输入绑定 | v-model |
value + onChange + onKeyDown |
| 生命周期 | onMounted |
useEffect(..., []) |
| 类型安全 | — | string[], HTMLInputElement |
🔁 转换说明
1. 状态管理:ref([]) → useState<string[]>([])
| Vue | React |
|---|---|
const codes = ref(new Array(6).fill('')) |
const [codes, setCodes] = useState<string[]>(new Array(6).fill('')) |
✅ 使用 TypeScript 泛型确保类型安全。
2. DOM 引用:ref([]) → useRef<(HTMLInputElement | null)[]>
Vue 中:
const codeInputs = ref([])
// v-for 中绑定 ref="codeInputs"
React 中需手动管理 ref 数组:
const codeInputs = useRef<(HTMLInputElement | null)[]>([])
// 在 render 中:
<input ref={(el) => { codeInputs.current[index] = el }} />
✅ 这是 React 中批量管理多个 DOM 引用的标准做法。
3. 生命周期:onMounted → useEffect(..., [])
useEffect(() => {
codeInputs.current[0]?.focus();
}, []);
✅ 确保只在挂载时聚焦第一个输入框。
4. 事件处理:@keydown → onKeyDown + onChange
为什么需要 onChange?
- Vue 的
v-model自动处理输入; - React 是受控组件,必须通过
value + onChange同步状态; - 仅靠
onKeyDown无法处理粘贴、拖拽、中文输入法等场景。
因此新增 handleChange:
const handleChange = (e, index) => {
const value = e.target.value;
if (/^\d?$/.test(value)) {
// 只接受 0 或 1 位数字
setCodes([...codes.slice(0, index), value.slice(-1), ...codes.slice(index + 1)]);
}
};
✅ 提升健壮性,防止用户粘贴 "123" 到单个输入框。
5. 键盘事件逻辑迁移
- 保留数字输入自动跳转;
- 保留 Backspace 回退;
- 阻止非数字输入(如字母、符号);
- 使用
setTimeout(..., 10)确保 DOM 更新后再聚焦(与 Vue 行为一致)。
6. 无障碍与语义化
添加 aria-label 提升可访问性:
aria-label={`Digit ${index + 1}`}
7. 样式完全复用
- 所有 Tailwind 类名直接迁移;
[caret-color:transparent]隐藏光标(设计需求);inputMode="numeric"触发移动端数字键盘。
✅ 注意事项
- 不要省略
onChange:否则粘贴内容会失效; - 使用回调 ref:避免
useRef数组在 re-render 时丢失引用; - 正则校验:
/^\d$/比e.key >= 0更可靠(避免特殊键干扰); - 防抖聚焦:
setTimeout确保 React 完成状态更新后再操作 DOM。
💡 可选优化
- 将完整验证码拼接为字符串:
const fullCode = codes.join(''); - 添加“重新发送”按钮;
- 支持鼠标点击聚焦任意输入框。
🎨 TailwindCSS 样式重点讲解
| 类名 | 作用 |
|---|---|
min-h-screen |
设置最小高度为视口高度,确保页面至少占满整个屏幕的高度 |
items-center / justify-center |
分别在交叉轴(垂直)和主轴(水平)上居中对齐子元素 |
overflow-hidden |
隐藏超出容器的内容,防止溢出导致滚动条出现 |
bg-gray-50 |
设置背景颜色为浅灰色,作为整体页面背景色 |
font-sans |
使用无衬线字体系列 |
max-w-2xl |
设置最大宽度为 2xl (72rem),限制主要内容区域的最大宽度 |
rounded-lg |
给元素添加较大的圆角半径 |
border-4 border-black |
添加黑色边框,边框宽度为 4px |
bg-white |
设置背景颜色为白色,用于主要内容区域的背景色 |
p-8 |
设置内边距为 8 个单位,提供足够的空间来分隔内容与边界 |
text-center |
文本居中对齐 |
mb-2 / mb-8 |
设置下外边距分别为 2 和 8 个单位,用于控制元素间的间距 |
text-2xl / text-sm |
设置文本大小为 2xl (1.5rem) 和 sm (0.875rem) |
font-bold |
设置字体加粗 |
text-gray-600 |
设置文本颜色为灰色,提供对比度但不过于强烈 |
flex-wrap |
允许子元素换行排列,适应不同屏幕尺寸 |
mx-1 my-2 |
分别设置左右外边距为 1 单位和上下外边距为 2 单位,用于输入框之间的间距 |
h-20 w-20 |
设置固定的高度和宽度为 20 个单位,定义输入框的大小 |
rounded-md |
给元素添加中等大小的圆角半径 |
border border-gray-200 |
添加浅灰色边框,增加视觉层次感 |
text-center |
文本居中对齐,适用于单字符输入框的设计 |
text-5xl font-light |
设置大号轻量级字体,使验证码数字更加突出 |
[caret-color:transparent] |
将光标颜色设置为透明,隐藏默认的闪烁光标 |
transition-all duration-300 |
应用所有属性的变化过渡效果,持续时间为 300ms |
focus:border-blue-500 focus:shadow-lg focus:shadow-blue-200/50 |
当输入框获得焦点时,改变边框颜色、添加阴影以增强视觉反馈 |
focus:outline-none |
移除默认的聚焦轮廓线,避免干扰设计的一致性 |
🦌 路由组件 + 常量定义
router/index.tsx 中 children数组中添加子路由
{
path: '/',
element: <App />,
children: [
...
{
path: '/VerifyAccountUi',
lazy: () =>
import('@/projects/VerifyAccountUi').then((mod) => ({
Component: mod.default,
})),
},
],
},
constants/index.tsx 添加组件预览常量
import demo41Img from '@/assets/pic-demo/demo-41.png'
省略部分....
export const projectList: ProjectItem[] = [
省略部分....
{
id: 41,
title: 'Verify Account Ui',
image: demo41Img,
link: 'VerifyAccountUi',
},
]
🚀 小结
通过这篇文章,我们使用 React19 和 TailwindCSS 创建一个直观且易于使用的验证码输入界面。通过合理利用 react的useState和 Tailwind CSS 的强大样式能力,我们可以轻松地构建出既美观又功能强大的前端组件。
想要让你的验证码输入界面更加完善?考虑以下扩展功能:
✅ 表单提交验证:增加对用户输入验证码的验证逻辑。
✅ 错误提示:当验证码无效时显示相应的错误消息。
✅ 倒计时重发机制:模拟验证码过期后重新发送验证码的功能。
📅 明日预告: 我们将完成RangeSlider组件,一个可以自定义进度的范围滑块组件。🚀
感谢阅读,欢迎点赞、收藏和分享 😊
原文链接:https://blog.csdn.net/qq_44808710/article/details/149778543https://blog.csdn.net/qq_44808710/article/details/149779252https://blog.csdn.net/qq_44808710/article/details/149778543
每天造一个轮子,码力暴涨不是梦!🚀
更多推荐
所有评论(0)