用touched和valid两个布尔值定义表单校验四态:初始态(false/true)、校验失败(true/false)、校验通过(true/true)、隐式必空(false/false);状态转移由touch、validate等事件驱动,非条件判断;跨字段联动靠事件广播,ui仅响应状态。

用布尔型状态机管理复杂交互表单的校验流,关键不是堆砌“true/false”,而是把校验过程拆解成几个互斥、可验证、有明确触发条件的阶段——比如“未触碰”“正在校验”“校验通过”“校验失败”。每个阶段对应一个布尔状态(或组合布尔值),转移靠用户动作或规则变化驱动,不依赖深层嵌套判断。
用两个布尔值定义四个核心校验态
单个 boolean 很难表达校验的丰富语义。推荐用 touched 和 valid 两个布尔字段协同表达:
- touched = false, valid = true:初始态,字段空但未被操作,不显示错误(避免一进页面就红)
- touched = true, valid = false:用户动过且当前不合法,显示错误提示
- touched = true, valid = true:已操作且通过校验,可进入下一步
- touched = false, valid = false:极少见,通常用于隐藏字段或条件禁用字段——虽未触碰,但规则判定其必须为空才合法(如“非企业用户时税号必须为空”)
状态转移由事件而非条件判断驱动
不要写 if (value && !/^\d+$/.test(value)) valid = false;而是定义清晰事件,让状态响应它:
- 用户聚焦 → 触发 TOUCH 事件 → 设置 touched = true
- 用户输入后防抖结束 → 触发 VALIDATE 事件 → 执行规则,更新 valid
- 关联字段变更(如切换“是否企业”)→ 触发 RULES_UPDATED → 重新 validate 当前字段
- 提交按钮点击 → 触发 SUBMIT_ATTEMPT → 强制所有 touched = false 字段变为 touched = true,并批量校验
跨字段联动靠状态广播,不靠手动调用
当“密码”字段 valid 变为 true,不能手动去调用“确认密码”的 validate()。正确做法是:
- “密码”字段校验通过后,广播事件 PASSWORD_VALIDATED
- “确认密码”模块监听该事件,并检查自身值是否与密码一致 → 更新自己的 valid
- 所有监听都基于字段 key(如 "user.password", "user.confirmPassword"),不耦合组件实例
- 若“密码”被清空,“确认密码”自动收到 PASSWORD_CLEARED,设 valid = false 并清空 errorMessage
错误提示与提交控制直接绑定状态
UI 层不查规则,只读状态:
- errorMessage 显示逻辑 = touched && !valid ? message : null
- 提交按钮禁用逻辑 = form.fields.some(f => f.touched && !f.valid) || form.submitting
- 全局错误摘要 = 收集所有 touched && !valid 字段的 message,按 key 分组去重
不复杂但容易忽略:布尔状态机的价值不在“多酷”,而在让每个校验环节都有明确入口、出口和副作用边界。你不再问“现在该不该校验”,而是问“哪个事件发生了,我该切到什么态”。










