第一步是识别业务语义单元而非按字段数量切分;fieldset 应按真实业务边界分组,legend 必须存在且为首个子元素,避免跨模块混组,嵌套勿超3层,隐藏字段优先用 hidden 属性,多步骤表单共用一个 form。

用 fieldset 按真实业务边界分组,不是按字段数量切
拆分表单结构的第一步不是“把 20 个字段切成 4 个 div”,而是识别哪些字段属于同一个业务语义单元。比如「收货信息」里包含 city、street、postalCode,它们共用校验逻辑(地址格式)、共享错误提示(如 address_invalid),也常被后端合并在一个 DTO 字段里——这就该放进同一个 fieldset。
-
legend必须存在且是fieldset的第一个子元素;空<legend></legend>会让读屏器跳过整组 - 别把「发票抬头」「税号」「开户行」硬塞进「收货信息」组——它们归属财务模块,变更频率和协作方都不同
- 嵌套超过 3 层
fieldset时,Chrome 会出现渲染延迟,iOS Safari 对深层嵌套的 focus 流也支持不稳
隐藏字段用 hidden 属性,别靠 display: none
动态控制字段显隐时,hidden 和 display: none 行为完全不同:前者让浏览器彻底忽略该元素参与验证、提交、甚至 form.elements 遍历;后者只是视觉隐藏,FormData 仍会收集它,checkValidity() 也会校验它。
- 切换字段状态优先写
element.hidden = true/false,而不是操作 class 或 style - 如果后端必须接收空值字段(比如可选的
companyName),那就得用display: none+ 手动清空value,否则提交时可能带脏数据 - Safari 旧版本兼容性要注意:iOS 9.3+、macOS 10.11+ 才完全支持
hidden属性
多步骤表单必须共用一个 <form>,禁用多个独立 form 标签
每个 <form> 是独立提交上下文,FormData 不跨 form 合并,formdata 事件监听不到其他 form 的字段,而且路由跳转时容易丢失状态。整个流程应该只有一个 <form>,步骤切换靠 CSS 类或 hidden 控制显隐。
- 每步校验只检查当前可见字段:
form.querySelectorAll(':scope > :not([hidden]) input:invalid') - 提交前用
new FormData(form)一次性收集,后端按字段名前缀区分步骤(如step1_email、step2_company) - 别依赖
formdata事件做状态同步——它只触发于本form内部变化,跨步骤无效
国际化文案按变更频率和协作角色拆 JSON,不是按模块名硬切
把「用户管理」整个塞进 user.json 是典型反模式。如果「角色分配」页面的文案和权限系统强耦合,那它的文案就应该在 permission.json 里,否则改权限逻辑时大概率漏翻。
立即学习“前端免费学习笔记(深入)”;
-
common.json:登录页、首页、导航栏共用词(login、home、logout),由产品统一维护 -
form.json:所有表单校验文案(required、email_invalid、submit_success),前端+后端联调确认 -
error.json:HTTP 错误码、网络异常、权限拒绝等系统级提示(401_unauthorized、network_timeout),SRE 和前端共同定义
最常被忽略的是协作边界——文案谁来改、什么时候改、改了要不要发版,这些比文件大小和目录层级重要得多。



















