news 2026/6/16 0:03:05

HarmonyOS PC 实战之注册表单的状态设计——四个 @State 如何驱动完整的表单交互

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS PC 实战之注册表单的状态设计——四个 @State 如何驱动完整的表单交互

文章目录

    • 前言
      • 四个核心状态
      • formData:一个对象管所有字段
      • attempted:控制错误提示的显示时机
      • isFormValid:用 get 派生,不用 @State
      • 完整代码
      • 密码强度的派生计算
      • agreeTerms 和 isFormValid 的关系
      • 小结

前言

一个注册表单,看起来就是几个输入框,但要做到"该提示的时候提示、不该提示的时候不打扰、注册按钮灰色时不可点",背后需要好几个状态变量分工合作。

HarmonyOS PC 端的注册表单用四个@State变量就能把所有交互逻辑覆盖掉。这篇把这四个变量各自负责什么、怎么联动说清楚。

四个核心状态

@StateformData:FormData={// 存所有输入框的值username:'',email:'',password:'',confirmPassword:''}@StateagreeTerms:boolean=false// 用户协议是否勾选@StateshowPassword:boolean=false// 密码是否可见@Stateattempted:boolean=false// 用户是否点过提交

一个状态,一个职责,互不重叠。

formData:一个对象管所有字段

不要给每个输入框单独定义@State username@State email……六七个字段就六七个状态,冗余。

用一个接口对象统一管理:

interfaceFormData{username:stringemail:stringpassword:stringconfirmPassword:string}@StateformData:FormData={username:'',email:'',password:'',confirmPassword:''}

更新时展开赋值:

this.formData={...this.formData,username:newValue}

这样不会丢掉其他字段,也能触发响应式刷新。

attempted:控制错误提示的显示时机

"用户没有点提交就显示错误提示"是一个很差的体验——用户刚打开表单,还没输任何东西,就一片红,这让人很不舒服。

attempted就是解决这个问题的:

// 错误提示只在 attempted 为 true 后才显示if(this.attempted&&this.hasError(field.key)){Text(this.getErrorMsg(field)).fontColor('#EF4444')}

点击注册按钮时设attempted = true,这时才开始显示错误提示:

Button('注册账号').onClick(()=>{this.attempted=trueif(this.isFormValid){this.submitForm()}})

isFormValid:用 get 派生,不用 @State

表单是否填写完整、是否可提交,这不是一个需要存储的状态,而是从formData派生的计算结果:

getisFormValid():boolean{return!!(this.formData.username.trim().length>=2&&this.formData.email.includes('@')&&this.formData.password.length>=8&&this.formData.confirmPassword===this.formData.password&&this.agreeTerms)}

formDataagreeTerms任意一个变化,isFormValid自动重新计算,注册按钮的禁用状态跟着变。

完整代码

enumFormFieldKey{None,Username,Email,Password,ConfirmPassword}interfaceFieldConfig{key:FormFieldKey label:stringplaceholder:stringrequired:booleantype:'text'|'email'|'password'iconLeft:stringhint:stringerrorMsg:string}@Entry@Componentstruct PcFormLayoutPage{@Stateusername:string=''@Stateemail:string=''@Statepassword:string=''@StateconfirmPassword:string=''@StateshowPassword:boolean=false@StateshowConfirmPassword:boolean=false@Stateattempted:boolean=false@StatefocusedField:FormFieldKey=FormFieldKey.None fields:FieldConfig[]=[{key:FormFieldKey.Username,label:'用户名',placeholder:'4~20个字符,支持中英文',required:true,type:'text',iconLeft:'👤',hint:'',errorMsg:'用户名不能为空'},{key:FormFieldKey.Email,label:'电子邮箱',placeholder:'example@email.com',required:true,type:'email',iconLeft:'📧',hint:'',errorMsg:'请输入有效的邮箱地址'},{key:FormFieldKey.Password,label:'密码',placeholder:'至少8位,包含字母和数字',required:true,type:'password',iconLeft:'🔒',hint:'忘记密码?',errorMsg:'密码至少8位'},{key:FormFieldKey.ConfirmPassword,label:'确认密码',placeholder:'再次输入密码',required:true,type:'password',iconLeft:'🔒',hint:'',errorMsg:'两次密码不一致'},]getFieldValue(key:FormFieldKey):string{if(key===FormFieldKey.Username)returnthis.usernameif(key===FormFieldKey.Email)returnthis.emailif(key===FormFieldKey.Password)returnthis.passwordreturnthis.confirmPassword}hasError(key:FormFieldKey,val:string):boolean{if(!this.attempted)returnfalseif(!val||val.trim()==='')returntrueif(key===FormFieldKey.Email&&!val.includes('@'))returntrueif(key===FormFieldKey.Password&&val.length<8)returntrueif(key===FormFieldKey.ConfirmPassword&&val!==this.password)returntruereturnfalse}getErrorMsg(field:FieldConfig,val:string):string{if(!this.hasError(field.key,val))return''if(!val||val.trim()==='')returnfield.errorMsgif(field.key===FormFieldKey.Email)return'请输入有效的邮箱地址'if(field.key===FormFieldKey.Password)return'密码至少8位'if(field.key===FormFieldKey.ConfirmPassword)return'两次密码不一致'returnfield.errorMsg}getisFormValid():boolean{return!!(this.username.trim()&&this.email.includes('@')&&this.password.length>=8&&this.confirmPassword===this.password)}@BuilderformField(field:FieldConfig,value:string){Column({space:6}){// Label 行Row({space:4}){if(field.required){Text('*').fontSize(13).fontColor('#EF4444')}Text(field.label).fontSize(13).fontColor('#374151').fontWeight(FontWeight.Medium)Blank()if(field.hint){Text(field.hint).fontSize(11).fontColor('#3B82F6')}}.width('100%').alignItems(VerticalAlign.Center)// 输入区行Row({space:8}){Text(field.iconLeft).fontSize(16).fontColor(this.hasError(field.key,value)?'#EF4444':'#9CA3AF').width(20).textAlign(TextAlign.Center)TextInput({placeholder:field.placeholder,text:value}).layoutWeight(1).backgroundColor(Color.Transparent).border({width:0}).fontSize(14).placeholderColor('#C4C9D4').type(field.type==='password'?(field.key===FormFieldKey.Password?(this.showPassword?InputType.Normal:InputType.Password):(this.showConfirmPassword?InputType.Normal:InputType.Password)):(field.type==='email'?InputType.Email:InputType.Normal)).onChange((v)=>{if(field.key===FormFieldKey.Username){this.username=v}elseif(field.key===FormFieldKey.Email){this.email=v}elseif(field.key===FormFieldKey.Password){this.password=v}else{this.confirmPassword=v}}).onFocus(()=>{this.focusedField=field.key}).onBlur(()=>{this.focusedField=FormFieldKey.None})if(field.type==='password'){Text(field.key===FormFieldKey.Password?(this.showPassword?'🙈':'👁'):(this.showConfirmPassword?'🙈':'👁')).fontSize(16).fontColor('#9CA3AF').onClick(()=>{if(field.key===FormFieldKey.Password)this.showPassword=!this.showPasswordelsethis.showConfirmPassword=!this.showConfirmPassword})}// 校验状态图标if(this.attempted){Text(this.hasError(field.key,value)?'❌':'✅').fontSize(14)}}.height(48).padding({left:14,right:14}).backgroundColor(this.hasError(field.key,value)?'#FEF2F2':'#F9FAFB').borderRadius(10).border({width:1.5,color:this.hasError(field.key,value)?'#EF4444':this.focusedField===field.key?'#3B82F6':'#E5E7EB'}).animation({duration:150})// 错误提示if(this.attempted&&this.hasError(field.key,value)){Row({space:4}){Text('⚠').fontSize(11).fontColor('#EF4444')Text(this.getErrorMsg(field,value)).fontSize(11).fontColor('#EF4444')}}}.width('100%').alignItems(HorizontalAlign.Start)}build(){Scroll(){Column({space:0}){// 卡片容器Column({space:24}){// 标题Column({space:6}){Text('创建账号').fontSize(24).fontWeight(FontWeight.Bold).fontColor('#111827')Text('加入 HarmonyOS 开发者社区').fontSize(14).fontColor('#6B7280')}.alignItems(HorizontalAlign.Start).width('100%')// 表单字段ForEach(this.fields,(field:FieldConfig)=>{this.formField(field,this.getFieldValue(field.key))})// 提交按钮Column({space:12}){Button('注册账号').width('100%').height(48).backgroundColor(this.isFormValid?'#3B82F6':'#9CA3AF').borderRadius(10).fontSize(15).fontWeight(FontWeight.Medium).onClick(()=>{this.attempted=true})Row({space:6}){Text('已有账号?').fontSize(13).fontColor('#6B7280')Text('立即登录').fontSize(13).fontColor('#3B82F6').fontWeight(FontWeight.Medium)}.justifyContent(FlexAlign.Center)}.width('100%')}.padding({left:40,right:40,top:40,bottom:40}).backgroundColor(Color.White).borderRadius(20).shadow({radius:24,color:'#10000000',offsetY:8}).width('100%').constraintSize({maxWidth:480}).margin({left:'auto',right:'auto'})}.padding({left:24,right:24,top:48,bottom:48})}.width('100%').height('100%').backgroundColor('#F9FAFB')}}

密码强度的派生计算

密码强度是一个纯计算结果,不需要@State,用get派生:

getstrengthLevel():number{constp=this.formData.passwordletscore=0if(p.length>=8)score++if(/[A-Z]/.test(p))score++if(/[0-9]/.test(p))score++if(/[^A-Za-z0-9]/.test(p))score++returnscore// 0~4}

四条规则每满足一条 +1 分,满分 4 分。密码变化时strengthLevel自动重算,强度条跟着变色。

agreeTerms 和 isFormValid 的关系

勾选协议是注册的必要条件。在isFormValid里加上&& this.agreeTerms,协议没勾选时注册按钮始终灰色:

getisFormValid():boolean{returnfieldRulesAllValid&&this.agreeTerms}

用户没有勾选协议就点注册,attempted设为 true,错误提示出来了,但协议那行没有专门的错误提示——让协议 Checkbox 本身颜色变红就够了,不需要额外的提示文字。

小结

四个状态:formData存数据、agreeTerms存协议状态、showPassword控制密码可见性、attempted控制错误显示时机。派生计算不用状态存:isFormValidstrengthLevel都是get属性,自动跟着状态变化。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 23:59:50

系统架构设计师-计算机网络基础体系全梳理

一、引言计算机网络是系统架构设计师知识体系的核心基础&#xff0c;广泛应用于分布式系统设计、微服务通信架构、云原生网络规划、网络安全架构设计等多个核心考点模块&#xff0c;在历年软考高级系统架构设计师考试中分值占比约 8%-12%&#xff0c;是必须熟练掌握的前置知识。…

作者头像 李华
网站建设 2026/6/15 23:56:58

解决OpenWrt Dnsmasq常见问题:DHCP响应慢、日志刷屏与AdGuard Home兼容

OpenWrt Dnsmasq深度调优&#xff1a;解决DHCP响应延迟与日志污染的实战指南家庭网络环境中&#xff0c;OpenWrt作为一款高度可定制的路由器操作系统&#xff0c;其内置的Dnsmasq服务承担着DNS解析和DHCP分配的双重职责。但在实际部署中&#xff0c;用户常会遇到DHCP响应缓慢、…

作者头像 李华
网站建设 2026/6/15 23:54:53

百考通AI:智能问卷设计,精准分层适配,让调研工作迈入高效新时代

在学术研究、市场分析、教育评估等多元场景中&#xff0c;问卷调研始终是收集数据、洞察需求的核心手段。但传统问卷设计往往耗时费力&#xff0c;不仅需要精准把握调研逻辑&#xff0c;还要兼顾受众体验与问题科学性&#xff0c;无论是高校学子的论文调研、企业的市场摸排&…

作者头像 李华
网站建设 2026/6/15 23:44:24

SPT-AKI Profile Editor:3步掌握逃离塔科夫离线版终极存档编辑器

SPT-AKI Profile Editor&#xff1a;3步掌握逃离塔科夫离线版终极存档编辑器 【免费下载链接】SPT-AKI-Profile-Editor Программа для редактирования профиля игрока на сервере SPT-AKI 项目地址: https://gitcode.com/gh…

作者头像 李华