省市区三级联动需通过自定义事件实现解耦与流动:选省触发province:changed加载市,市加载完触发city:loaded通知区;统一用region-select等语义化事件通信,跨层级用事件总线或emit-cascade,初始化与交互共用region:load事件流。

省市区三级联动不是简单堆砌三个下拉框,关键在于让选择行为真正“流动”起来——选省触发市更新,选市触发区更新,而自定义事件正是这条流动路径的“开关”和“信使”。它把原本耦合在DOM操作或接口调用里的逻辑解耦出来,让每一级的选择结果能被明确捕获、传递和响应。
用自定义事件代替硬编码回调
很多初版实现会直接在省级选择的 change 事件里写死请求城市列表、渲染 select、再绑定市级 change,层层嵌套。一旦要加校验、缓存或异步失败重试,代码就迅速失控。换成自定义事件后,逻辑变清晰:
- 省级选择后,不直接操作市级 DOM,而是触发 province:changed 事件,并携带 provinceId
- 监听该事件的模块负责发起请求、处理缓存、更新市级下拉项
- 市级更新完成,再触发 city:loaded 事件,通知区级模块准备就绪
父子组件通信中统一事件命名规范
在 Vue、微信小程序或自定义组件中,子组件(如 ProvincePicker)不应直接修改父组件的数据,而应通过自定义事件上抛意图:
- 使用语义化名称,如 region-select 而非 change,避免与原生事件冲突
- 携带结构化数据:detail: { level: 'province', code: '110000', name: '北京市' }
- 父组件统一处理所有 region-select 事件,根据 level 字段决定是加载下一级,还是提交最终值
跨层级状态同步靠事件总线或 emit-cascade
当联动嵌套较深(比如带街道四级),或涉及多个独立模块(地址编辑页 + 物流配置页),单靠父子 emit 不够用:
- 轻量场景:用简单的事件总线(如一个空 Vue 实例或 mitt 库),发布 region:update 并附带完整路径 [110000, 110100, 110105]
- 复杂场景:采用 emit-cascade 模式——子组件 emit 后,父组件拦截并重新 emit 带上下文的事件,如从 DistrictPicker 发出 district:selected,CityPicker 拦截后 emit region:confirmed,确保顶层只关心终态
回显与初始化也走同一事件通道
表单编辑时,已有地区编码需要反向展开为省市区文字。如果初始化逻辑和用户交互逻辑走两套路径,极易出现不一致:
- 统一触发 region:load 事件,参数为编码数组或字符串(如 "110000/110100/110105")
- 监听者解析编码,逐级设置默认值,并自动触发下一级加载(例如设好省ID后,自动拉取并设好市ID)
- 整个过程对用户透明,且与用户手动选择共享同一套事件流和错误处理机制










