原生 JS 打造三级联动

后台表单、招生官网、招聘系统都离不开「省-市-学校」三连下拉。本文用 js原生代码实现一套零依赖、可复用、可扩展的三级联动,数据与渲染彻底解耦,换一批数据也能秒上线。

一、数据约定

把全国行政区划抽象成三层嵌套对象:

js 复制代码
const province = { '11': '北京市', '12': '天津市', ... }
const city = {
  '11': { '014': '天津市' },
  '12': { 
    '015': '漳州市', 
    '016': '厦门市',
    ... 
  }
}
const allschool = {
  '015': ['闽南师范大学', '厦门大学嘉庚学院', ...],
  '016': ['厦门大学', '集美大学', ...]
}
  • 一级 key → 省编号
  • 二级对象 → 市编号 : 市名称
  • 三级数组 → 学校名称列表

数据结构一旦固定,渲染逻辑永远不变。

二、链式渲染三步走

1.初始化:渲染省

js 复制代码
for (const code in province) {
  provinceDOM.append(new Option(province[code], code))
}

new Option(text, value)createElement('option') 少写两行,还能自动映射 <option> 的 text 与 value。

2.省变化 → 渲染市 + 默认学校

js 复制代码
provinceDOM.onchange = () => {
  cityDOM.innerHTML = schoolDOM.innerHTML = ''   // 清空下游
  const cityObj = city[provinceDOM.value]
  if (!cityObj) return                           // 边界:无数据
  for (const code in cityObj) {
    cityDOM.append(new Option(cityObj[code], code))
  }

  // 立即渲染默认市下的学校
  const schoolArr = allschool[cityDOM.value] || []
  schoolArr.forEach(name => schoolDOM.append(new Option(name)))
}

清空下游是防止「幽灵选项」:用户先选北京,再选天津,若不重置,城市下拉会残留「和平区」。

3.市变化 → 只渲染学校

js 复制代码
cityDOM.onchange = () => {
  schoolDOM.innerHTML = ''
  const schoolArr = allschool[cityDOM.value] || []
  schoolArr.forEach(name => schoolDOM.append(new Option(name)))
}

市变化时不再清空省,因为上游已经确定;只处理下游,减少 DOM 抖动。

三、边界与优化细节

省无数据

if (!cityObj) return 直接退出,下拉保持空白,避免后续报错。

市无学校

|| [] 兜底,防止 undefined.forEach 抛错;用户看到的是空下拉,逻辑一致。

异步数据

onchange 换成 fetch('/api/city/' + provCode).then(...) 即可接入后端分页,前端逻辑不变。

键盘可用

<select> 原生支持上下键、回车、空格,无需额外代码即可满足无障碍需求。

四、代码示例

1.html骨架

html 复制代码
  <select id="province"></select>
  <select id="city"></select>
  <select id="school"></select>

2.核心js

js 复制代码
const p = document.getElementById('province')
const c = document.getElementById('city')
const s = document.getElementById('school')

// 1. 渲染省
for (const code in province) p.append(new Option(province[code], code))

// 2. 省变化 → 市 + 默认学校
p.onchange = () => {
  c.innerHTML = s.innerHTML = ''
  const cityObj = city[p.value]
  if (!cityObj) return
  for (const code in cityObj) c.append(new Option(cityObj[code], code))
  const schoolArr = allschool[c.value] || []
  schoolArr.forEach(name => s.append(new Option(name)))
}

// 3. 市变化 → 学校
c.onchange = () => {
  s.innerHTML = ''
  const schoolArr = allschool[c.value] || []
  schoolArr.forEach(name => s.append(new Option(name)))
}
相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万2 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋2 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
晨米酱2 天前
AGENTS.md:Agent 的上下文策略层
面试·架构·agent
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄2 天前
JavaScript 隐式全局变量解析
javascript
彧azz2 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端