onblur比onchange更适合省市区三级联动,因用户常通过tab或鼠标跳转导致onchange不触发,而onblur能可靠捕获焦点离开动作以加载下级数据;需配合受控模式、正确useeffect依赖、空数组兜底及准确form.item name路径。

为什么直接用 onchange 绑定三级联动会漏校验?
因为用户可能跳过中间级(比如直接选“北京市”后手动填“朝阳区”,不触发市级 onchange),或用键盘输入、粘贴、自动填充绕过事件监听。纯前端依赖事件绑定的校验,在表单提交时大概率失效。
真正可靠的校验必须在 submit 时统一检查,且需覆盖三种非法状态:省为空但市非空、市为空但区非空、三级值存在但组合不存在(如“浙江-深圳-南山区”)。
- 用
FormData或直接读取select.value获取当前三字段真实值,别信selectedIndex或options[0].selected - 后端返回的省市区数据必须带唯一编码(如
adcode),前端校验时比对编码而非文字,避免“北京”和“北京市”歧义 - 若使用第三方库(如
china-division),确认其数据含三级完整父子关系字段(如province.code、city.provinceCode、district.cityCode)
如何用原生 JS 实现提交时一次性校验?
核心是构建一个可查的三级映射结构,不是靠 DOM 层级联动逻辑来推断合法性。例如:
const areaMap = {
"110000": { // 北京市 code
cities: {
"110100": { // 北京市辖区 code
districts: ["110101", "110102", "110105"] // 东城、西城、朝阳...
}
}
}
}
提交时校验逻辑:
- 先检查
provinceSelect.value是否在Object.keys(areaMap)中 - 再检查
citySelect.value是否属于areaMap[provinceSelect.value]?.cities的 key - 最后检查
districtSelect.value是否在areaMap[provinceSelect.value]?.cities[citySelect.value]?.districts数组里 - 任一不满足,调用
districtSelect.setCustomValidity("请选择有效的区")并districtSelect.reportValidity()
为什么用 setCustomValidity 而不用 alert 或 class 切换?
因为只有 setCustomValidity 能真正阻断原生表单提交,并和 reportValidity() 配合触发浏览器默认错误样式与焦点定位,用户体验一致,且适配屏幕阅读器。
- 空字符串
""表示校验通过;非空字符串(如"请选择所在区")表示失败 - 必须对每个
select单独调用setCustomValidity,不能只设最后一个 - 注意:Chrome 对
select的reportValidity()支持良好,但 Safari 在某些版本中需配合focus()才能准确定位
后端返回的数据结构不标准怎么办?
常见情况是后端只给扁平数组,如 [{code:"110101", name:"东城区", province:"北京", city:"北京市辖区"}]。这时前端必须预处理,否则每次校验都要遍历全量数组,性能差且易出错。
- 在获取数据后立即构建树形结构:
buildAreaTree(data),缓存到const areaTree = {...} - 避免在
submit事件里重复解析 —— 提交可能高频触发(比如用户连点) - 如果后端连
province/city字段都不提供,只有adcode,那就必须依赖adcode前两位=省、前四位=市的规则(如110000→北京,110100→北京市辖区),但要注意内蒙古、新疆等有双字简称的例外
最常被忽略的是港澳台及直辖市下属区的归属逻辑 —— 它们没有“市”一级,但数据结构仍要兼容三级字段。校验时得允许 city === "" 且 province in ["110000","310000","120000","500000"] 时,district 直接挂到 province 下。这个分支不加,上线后一定被用户报 bug。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











