鸿蒙 HarmonyOS 6 | TextInput组件 ONE_TIME_CODE 验证码输入实战
鸿蒙 6 在 API 20 里给文本输入组件补进了验证码输入模式 `ONE_TIME_CODE`。这项能力同时覆盖 `TextInput`、`TextArea` 和 `Search`,其中 `TextInput` 是最典型的落地入口。它的作用很直接,把验证码输入这件事从普通文本输入里单独拎出来,让系统知道这是一次性验证码场景,再围绕这个语义去做输入法和自动填充上的协同。
前言
验证码输入是登录、注册、密码找回、支付确认里最常见的一类交互。这个场景看起来简单,真正做起来,体验差距却很明显。输入框类型选得不对,系统就不会把它识别成验证码场景,输入法优化、自动填充、无障碍提示这些能力都很难接得顺。
鸿蒙 6 在 API 20 里给文本输入组件补进了验证码输入模式 ONE_TIME_CODE。这项能力同时覆盖 TextInput、TextArea 和 Search,其中 TextInput 是最典型的落地入口。它的作用很直接,把验证码输入这件事从普通文本输入里单独拎出来,让系统知道这是一次性验证码场景,再围绕这个语义去做输入法和自动填充上的协同。

一、为什么验证码输入要单独设一种类型
过去很多项目直接拿普通输入框收验证码,功能上当然能用,但系统眼里它只是一个普通文本框。这样带来的问题也很现实。输入法未必会切到最合适的键盘,自动填充也不容易精准命中,无障碍场景下对输入意图的表达也不够明确。
API 20 把 ONE_TIME_CODE 放进输入类型枚举之后,验证码场景终于有了单独的语义标记。华为开发者文档和系统新增能力说明都已经把这一点写清楚了,ONE_TIME_CODE 属于验证码类型输入模式,在 API 20 起可用。对 TextInput 来说,它对应的是一个明确的 InputType 枚举成员。
这类语义化输入类型真正有价值的地方,在于它不只是改一个键盘样式。系统能据此判断,当前这个输入框承接的是验证码,而不是用户名、普通数字或密码。后面不管是自动填充、输入法联动,还是安全场景下的输入优化,前提都在这里。
二、处理基础接法
TextInput 这条线的接法很直接,输入框正常创建,然后把 type 设成 InputType.ONE_TIME_CODE。验证码通常有固定位数,所以 maxLength 也应该一起配上。6 位验证码是最常见的配置,4 位和 8 位也都很常见,关键是跟业务规则对齐。
下面这段代码就是最基础、最适合直接落进项目里的写法:
@Entry
@Component
struct VerifyCodeDemo {
@State verificationCode: string = ''
build() {
Column({ space: 20 }) {
TextInput({
placeholder: '请输入验证码',
text: this.verificationCode
})
.type(InputType.ONE_TIME_CODE)
.maxLength(6)
.onChange((value: string) => {
this.verificationCode = value
})
}
.width('100%')
.padding(24)
}
}
如果这个页面本身就是典型的验证码验证页,还可以把自动填充动画打开。enableAutoFillAnimation 也已经进了 TextInput 这条能力线,适合配合系统自动填充一起用,让验证码被系统填进来时,过渡更自然。
TextInput({
placeholder: '请输入验证码',
text: this.verificationCode
})
.type(InputType.ONE_TIME_CODE)
.maxLength(6)
.enableAutoFillAnimation(true)
.onChange((value: string) => {
this.verificationCode = value
})
如果页面还有手机号输入框,最顺的写法就是把手机号和验证码输入类型一起配清楚。手机号走 PhoneNumber,验证码走 ONE_TIME_CODE,两个输入场景分开,系统才能各自做对优化。
@Entry
@Component
struct LoginByCodePage {
@State phoneNumber: string = ''
@State verificationCode: string = ''
build() {
Column({ space: 16 }) {
TextInput({ placeholder: '请输入手机号', text: this.phoneNumber })
.type(InputType.PhoneNumber)
.maxLength(11)
.onChange((value: string) => {
this.phoneNumber = value
})
TextInput({ placeholder: '请输入验证码', text: this.verificationCode })
.type(InputType.ONE_TIME_CODE)
.maxLength(6)
.onChange((value: string) => {
this.verificationCode = value
})
}
.padding(24)
}
}
三、最佳实践
验证码输入体验真正拉开差距的地方,不在基础接入,在输入完成后的处理逻辑。
第一件事,是长度控制和自动提交。验证码一旦达到固定位数,通常就可以直接触发校验,不需要让用户再多点一次按钮。这个动作很适合放在 onChange 里做,但要注意只在长度满足时触发一次,不要每次输入都重复请求。
@State verificationCode: string = ''
private verifyCode(): void {
// 执行验证码校验
}
TextInput({ placeholder: '请输入验证码', text: this.verificationCode })
.type(InputType.ONE_TIME_CODE)
.maxLength(6)
.onChange((value: string) => {
this.verificationCode = value
if (value.length === 6) {
this.verifyCode()
}
})
第二件事,是错误状态要收得干净。验证码输错之后,提示要清楚,但不能一直挂着不消失。用户一旦重新输入,错误提示最好立刻清空,这样反馈会更连贯。showError 本身就是 TextInput 的现成能力,适合直接拿来做这层状态。
@State verificationCode: string = ''
@State errorMessage: string = ''
TextInput({ placeholder: '请输入验证码', text: this.verificationCode })
.type(InputType.ONE_TIME_CODE)
.maxLength(6)
.showError(this.errorMessage)
.onChange((value: string) => {
this.verificationCode = value
this.errorMessage = ''
})
第三件事,是倒计时和重新获取按钮要跟输入框节奏一致。验证码输入框只是流程的一部分,真正完整的交互还包括获取验证码、倒计时、重发控制和提交动作。页面如果只把输入框做对了,其他链路还很生硬,体验还是会断。
下面这段代码就是一个更接近真实业务页面的骨架:
@Entry
@Component
struct SmsLoginPage {
@State phoneNumber: string = ''
@State verificationCode: string = ''
@State countdown: number = 0
@State errorMessage: string = ''
private canSendCode(): boolean {
return this.phoneNumber.length === 11 && this.countdown === 0
}
private canLogin(): boolean {
return this.phoneNumber.length === 11 && this.verificationCode.length === 6
}
private requestVerificationCode(): void {
this.countdown = 60
// 这里补发码逻辑和倒计时逻辑
}
private performLogin(): void {
// 这里补登录逻辑
}
build() {
Column({ space: 20 }) {
TextInput({ placeholder: '请输入手机号', text: this.phoneNumber })
.type(InputType.PhoneNumber)
.maxLength(11)
.onChange((value: string) => {
this.phoneNumber = value
})
Row({ space: 12 }) {
TextInput({ placeholder: '请输入验证码', text: this.verificationCode })
.type(InputType.ONE_TIME_CODE)
.maxLength(6)
.layoutWeight(1)
.showError(this.errorMessage)
.enableAutoFillAnimation(true)
.onChange((value: string) => {
this.verificationCode = value
this.errorMessage = ''
if (value.length === 6) {
this.performLogin()
}
})
Button(this.countdown > 0 ? `${this.countdown}s` : '获取验证码')
.enabled(this.canSendCode())
.onClick(() => {
this.requestVerificationCode()
})
}
Button('登录')
.width('100%')
.enabled(this.canLogin())
.onClick(() => {
this.performLogin()
})
}
.padding(24)
}
}
四、容易踩坑的点
第一个坑,是把验证码输入框继续当普通数字输入框用。验证码看起来大多是数字,但语义并不一样。Number 只表达数字输入,ONE_TIME_CODE 表达的是一次性验证码场景。项目里如果目标是做短信验证码、邮箱验证码、支付确认码,这两个类型不要混着用。
第二个坑,是把验证码当密码框处理。验证码输入通常需要用户可见,便于确认输入结果,所以不适合直接套密码隐藏逻辑。安全防护重点应该放在日志、内存和流程控制上,而不是把验证码显示成星号。
第三个坑,是忽略无障碍语义。验证码输入场景对屏幕阅读器来说,最好有明确提示。输入框的 placeholder 和 accessibilityText 应该把“验证码”“位数”“数字输入”这些关键信息说清楚,别让无障碍用户只能猜。
TextInput({ placeholder: '6位数字验证码', text: this.verificationCode })
.type(InputType.ONE_TIME_CODE)
.maxLength(6)
.accessibilityText('验证码输入框,请输入6位数字验证码')
第四个坑,是兼容性判断做得太晚。ONE_TIME_CODE 是 API 20 新增能力,如果项目需要兼容更低版本系统,最好在输入组件适配层就把这件事统一处理掉。新版本走 ONE_TIME_CODE,旧版本再回退到普通数字输入。这样页面层不需要知道太多细节,后面维护会轻很多。
总结
鸿蒙 6 API 20 给 TextInput 补进来的 ONE_TIME_CODE,看起来只是多了一个输入类型,真正改变的是验证码输入这类场景终于有了明确的系统语义。系统知道这是验证码输入之后,输入法优化、自动填充协同、无障碍提示这些能力才有了稳定的基础。
更多推荐




所有评论(0)