做地图类的可视化项目,最容易被低估的一个需求就是"我要点哪块亮哪块"。看起来一句dispatchAction就能搞定的事,实际落到echarts的geo组件上,很多人第一次都是懵的:鼠标点上去有反应,emphasis悬停色也正常,但只要想让它"选中后一直亮着",或者想在代码里动态指定高亮显示区域(比如从外部列表、搜索框、URL 参数传进来一串地区名),地图就跟没听见一样纹丝不动。这篇就把echarts geo地图的select机制从头拆一遍,讲清楚它为什么不生效、动态高亮有几条路可走、每条路各自会踩什么坑,以及怎么和外部组件做双向联动。内容偏向有 ECharts 基础但被 geo 选中态折磨过的开发者,纯新手也能跟着抄配置跑起来。
1. 先弄清楚 geo 组件和 series-map 到底谁在管选中态
1.1 同一个地图,两套完全不同的渲染链路
很多人对地图的认知是从series: [{ type: 'map' }]开始的,那套东西确实省心:selectedMode: 'single'一开,点一下自动高亮,再点一下自动取消,dispatchAction({ type: 'mapSelect' })也能正常点亮指定区域。于是当项目里换成geo组件之后,大家会下意识认为"这不就是换个写法嘛",把selectedMode从 series 里挪到 geo 里,然后发现——啥也没变,还是点不动。
根本原因在于,这两个东西在 ECharts 内部的定位完全不同。series-map是一个数据系列,它的本职工作就是"把一份数据渲染成地图",所以它天然要处理数据的选中语义,选中态是它内置能力的一部分。而geo是一个坐标系组件,它的出现是为了给散点图、飞线图、热力图这些东西提供一个"地理底图"当作 canvas,它的核心职责是坐标映射(经纬度 → 屏幕像素)和视野控制(缩放、平移)。在原始设计里,geo 只负责"画"和"算位置",至于这一块区域是不是被选中了、选中了要长什么样,它并不想管。
这个设计差异带来的直接后果就是:你在series-map上习以为常的那套选中逻辑,在 geo 上全部需要显式打开。不是配置写错了,而是它默认关着。
1.2 selectedMode 默认值是问题的源头
真正卡住九成人的那一行,是geo.selectedMode。它的默认值是false,意思是"这个坐标系不参与选中交互"。这个默认值在后面的版本里依然如此,所以你光配geo.select的样式是没用的——样式配了,但没有任何东西会进入 select 状态。
我一般把它当成一个总开关来看:
geo: { map: '江北省', selectedMode: 'single' // false | 'single' | 'multiple',默认 false }false:不响应任何选中交互,dispatchAction里的geoSelect也会被忽略。'single':单选用,常用于"点一个地区看详情"的场景。'multiple':多选用,适合"勾选多个地区做对比"的场景。
提示:
selectedMode只是打开开关,真正决定"亮起来长什么样"的是geo.select这个状态样式块。这两个必须成对出现,只配一个都是白干。
还有一个容易被搞混的点:geo.selectedMode管的是点击交互,而geoSelect这个 action 管的是代码主动设置选中。两者不冲突,但前提是selectedMode不能是false,否则 action 派发下去会被静默丢弃,连报错都不给你。
1.3 一张表说清两者的能力边界
我在项目里踩过几次之后,干脆整理了一张对照表,后面选型就不用再翻文档了:
| 能力项 | series-map | geo 组件 |
|---|---|---|
| 默认渲染地图 | 是,天然自带 | 是,但主要用于当底图 |
| 点击自动选中 | selectedMode打开即可 | 需selectedMode+select样式 |
| 代码设置选中 | mapSelectaction | geoSelectaction |
| 选中事件名 | mapselectchanged | geoselectchanged |
click 事件componentType | 'series' | 'geo' |
| 能否承载散点/飞线 | 不能,需要再挂 geo | 可以,这是它的主场 |
| 选中态默认样式 | 有内置高亮 | 基本等于没有,得自己写 |
看最后两行就明白了:如果你的地图上只有颜色区块,用series-map最省事;一旦地图上要叠散点、飞线、热力、路线,你必然绕不开 geo。而 geo 的代价就是——选中态这套东西,得自己一件一件装回去。
另外提一句,很多人搜到过"geo 地图动态 select"但照着写还是不动,最常见的原因就是地图上同时存在 geo 和 series-map 两份渲染。你以为点在 geo 上,实际点到的可能是上面那层 series;事件回调里componentType是'series',你的if (params.componentType === 'geo')判断直接把逻辑拦掉了。这个坑我在第 5 节会展开。
2. 搭一个能点击、能高亮的底图:GeoJSON 到 registerMap 的完整链路
2.1 地图数据从哪来,名称字段为什么是命门
ECharts 自己不带地图数据,你得先把一份 GeoJSON 注册进去。这份数据可以来自公开的行政区划边界数据、内部 GIS 系统导出的文件,或者自己用工具转换出来的结果。不管来源是哪,有一个字段必须重点确认:properties.name。
ECharts 在渲染时,会把每一块多边形和它properties.name里那个字符串绑定起来,之后所有的选中、样式覆盖、事件回调,用的都是这个字符串。也就是说,如果 GeoJSON 里写的是"江北省",而你dispatchAction传的是"江北",两边对不上,那么这次操作会被安静地丢掉——没有警告、没有异常,地图就是不动。这类问题排查起来特别费时间,因为它看起来像"代码没执行",实际上代码执行了,只是没匹配上。
我的习惯是在注册地图之后,先把所有区域名打印一遍,跟业务侧的下拉框、列表数据做一次比对:
// 假设 geojsonData 是已经解析好的 GeoJSON 对象 const regionNames = geojsonData.features .map(f => f.properties && f.properties.name) .filter(Boolean); console.log('地图区域总数:', regionNames.length); console.log('前十个区域名:', regionNames.slice(0, 10));echarts.registerMap('江北省', geojsonData);注意:
registerMap是全局注册,同一个名字重复注册会覆盖前一次。如果你做省市区下钻,不要用同一个名字反复注册不同层级的数据,否则缓存和异步加载会互相打架。我一般用层级前缀 + 行政区代码这种命名方式来做键。
2.2 registerMap 之后必须做的两件事
注册完地图,别急着堆业务逻辑,先把下面两件事做了,能省掉后面一半的调试时间。
第一件是确认区域数量。GeoJSON 里偶尔会混进一些空几何、或者name为空的要素,它们在图上不可见但会占位,导致你以为"少了一块"或者"多了一块"。数量对不上时,先把空name的要素过滤掉。
第二件是统一名称口径。业务数据里的地区名经常带后缀,比如列表里写"云溪市",GeoJSON 里写"云溪",或者反过来。这个不一致必须在地图渲染之前解决掉,绝不能靠"在事件里做模糊匹配"来兜底——模糊匹配在"东湖区"和"东湖新区"这种命名上会直接给你匹配错。
// 建一份名称映射表,把业务名统一成地图名 const nameMap = { '云溪市': '云溪', '东湖新区': '东湖区' }; const toMapName = bizName => nameMap[bizName] || bizName;2.3 一份可以直接抄的 geo 基础配置
下面这份配置是我在多个项目里沉淀下来的基础版,select和emphasis用了不同色系,方便区分"悬停"和"选中"两种状态:
const chart = echarts.init(document.getElementById('map')); const option = { tooltip: { trigger: 'item', formatter: params => (params.name ? `${params.name}` : '') }, geo: { map: '江北省', roam: true, // 允许缩放与平移 zoom: 1.1, selectedMode: 'single', label: { show: true, fontSize: 11, color: '#475569' }, itemStyle: { areaColor: '#eef2f7', borderColor: '#c7d2e0', borderWidth: 0.8 }, emphasis: { label: { color: '#ffffff' }, itemStyle: { areaColor: '#60a5fa', borderColor: '#2563eb' } }, select: { label: { color: '#ffffff' }, itemStyle: { areaColor: '#1d4ed8', borderColor: '#1e3a8a' } }, regions: [] }, series: [] }; chart.setOption(option);这里有三点值得说明。roam: true打开后用户能拖动和滚轮缩放,好处是看得清,坏处是缩放后标签会挤成一团,这个后面第 5 节单独讲处理办法。label.show在小屏上要谨慎,区域密集的地方标签会重叠,我通常按缩放层级动态控制。regions: []这个空数组不是摆设,它是后面"第二条路"要用的挂载点,先留好位置。
配置跑起来之后,你应该能看到一张全国/全省的底图,鼠标划过有浅蓝的悬停色。这时候点击是没反应的——因为交互逻辑还没接。
3. 动态指定高亮区域的三种做法,以及各自会踩什么坑
3.1 dispatchAction + geoSelect:官方正路
最正统、也最推荐的做法是派发 action。它的好处是选中态由 ECharts 内部维护,你不用操心状态同步,select样式也会被正确应用。
// 点亮一个区域 chart.dispatchAction({ type: 'geoSelect', geoIndex: 0, // 只有一个 geo 时可以省略 name: '云溪' }); // 取消点亮 chart.dispatchAction({ type: 'geoUnSelect', geoIndex: 0, name: '云溪' });实际项目里我不会零散地调这两句,而是包一层选中集合的管理函数。原因很简单:'single'模式下,连续调两次geoSelect不保证前一个会自动取消。这个行为在不同版本上表现有差异,我遇到过"点 A 亮了,再点 B,A 还亮着"的情况,页面上两个区域同时高亮,用户一眼就看出不对。稳妥的做法是自己记一份选中集,切换之前先清干净:
let selectedNames = []; function selectRegion(name) { chart.dispatchAction({ type: 'geoSelect', geoIndex: 0, name: name }); if (!selectedNames.includes(name)) { selectedNames.push(name); } } function unselectRegion(name) { chart.dispatchAction({ type: 'geoUnSelect', geoIndex: 0, name: name }); selectedNames = selectedNames.filter(n => n !== name); } // 外部传入一串区域名,做批量高亮 function highlightBatch(names) { const prev = selectedNames.slice(); // 先拷贝,避免循环中改数组 selectedNames = []; prev.forEach(unselectRegion); // 清空旧状态 names.forEach(selectRegion); // 统一点亮新状态 } highlightBatch(['云溪', '东湖区', '南岭']);这套写法有两个关键点。一是prev.forEach(unselectRegion)之前必须先slice(),因为unselectRegion内部会改动selectedNames,直接遍历原数组会出现漏项。二是在清空后再统一点亮,能保证无论调用多少次,页面上的高亮集合始终等于你传进来的这组名字,不会出现"幽灵高亮"。
还有个细节:dispatchAction要在setOption完成之后调用。如果你在同一个 tick 里先setOption再派发,绝大多数情况下没问题,但涉及地图数据切换(比如从省级下钻到市级)时,等一次rendered事件或放进requestAnimationFrame更保险。
3.2 直接改 regions:改得快,脏得也快
第二条路是不碰选中态,直接改geo.regions里的样式。GeoJSON 里所有的区域名你都已经拿到了,那就可以把它们全部铺进regions,命中高亮的给深色,其余给回默认色:
const allRegionNames = geojsonData.features .map(f => f.properties && f.properties.name) .filter(Boolean); function paintByRegions(highlightNames) { const set = new Set(highlightNames); const regions = allRegionNames.map(name => ({ name: name, itemStyle: { areaColor: set.has(name) ? '#1d4ed8' : '#eef2f7', borderColor: '#c7d2e0' }, label: { color: set.has(name) ? '#ffffff' : '#475569' } })); chart.setOption({ geo: { regions: regions } }); } paintByRegions(['云溪', '东湖区']);这条路最大的优点是"所见即所得",你想给某块加描边、加阴影、加自定义标签,直接写就行,不受 select 状态样式的限制。缺点也非常明显。
第一个缺点是你必须每次都传全量。ECharts 对regions数组是按索引做合并的,如果你这次只传两个命中的区域,上次传的样式不会自动清掉,会残留。所以要么全量铺,要么setOption(option, true)整体重设——但后者会把缩放、平移位置一起重置,体验很差。我一般在区域数量可控(几百个以内)时用全量铺的方式。
第二个缺点是它和选中态是两套东西。你用regions染了色,但selectedMode一旦开着,用户自己去点地图,又会叠加一层 select 样式,最终看到的是两套颜色打架。所以我的建议很明确:如果走regions这条路,就把selectedMode关掉,所有高亮全部由你控制的regions渲染,同时自己接管 click 事件做状态切换。逻辑统一,不容易出乱子。
还有一个折中玩法:regions里每个元素可以带一个selected: true字段,用它来做初始选中态。这个字段我在部分版本上遇到过不生效的情况(尤其是和其他状态样式混用的时候),所以我现在更倾向于:基础态用regions铺,初始选中态统一在setOption之后用geoSelect补一刀。这样行为最可预期。
3.3 靠 selectedMode 多选做"批量点亮"
如果你的场景本来就是"允许用户选中多个地区",那就把selectedMode设成'multiple',然后让 ECharts 自己去管这些状态,你只监听geoselectchanged事件来同步外部 UI。
geo: { selectedMode: 'multiple', select: { itemStyle: { areaColor: '#1d4ed8', borderColor: '#1e3a8a' }, label: { color: '#ffffff' } } } chart.on('geoselectchanged', function (params) { // params.selected 通常是当前所有选中区域名的数组 console.log('当前选中区域:', params.selected); });这条路的省心之处在于"取消选中"逻辑不用你写,用户再点一次自动取消。但要注意,geoselectchanged的事件参数在不同版本里字段名略有差异,稳妥做法是先console.log(params)看一眼结构,再用里面的字段,别照抄网上的代码。
多选模式下还有个体感问题:区域多的时候,用户点了七八个会有明显的实时重绘卡顿。如果只做展示不做频繁交互,我宁愿用'single'模式,配合外部的多选框组件来驱动regions全量重绘,反而更流畅。
3.4 三种方案横向对比与选型建议
| 方案 | 状态管理方 | 优点 | 主要坑点 | 适用场景 |
|---|---|---|---|---|
geoSelectaction | ECharts 内部 | 官方正路、样式可控 | 需手动清旧选中集;selectedMode必须开 | 点击交互为主、单/多选明确 |
改geo.regions | 你自己的代码 | 样式完全自由、可加描边阴影 | 必须全量传;和 select 态会打架 | 外部数据驱动的高亮、需精细样式 |
selectedMode: 'multiple' | ECharts 内部 | 取消逻辑免写 | 大区域量下重绘卡顿 | 用户自主勾选、区域数不多 |
我的默认选择是:以点击交互为主就用geoSelect,以外部数据驱动(搜索、筛选、列表联动)为主就用regions,两者同时存在时让regions负责基础染色、geoSelect负责当前焦点。这个"分层"的思路能避免大部分状态冲突。
4. 事件那头怎么接:从 click 到双向联动的完整闭环
4.1 click 回调里 params 到底给了什么
geo 上的点击事件,回调参数里最关键的三个字段是componentType、name和region。componentType用来判断这次点击来自哪一层——geo 是'geo',series-map 是'series',散点是'series'且seriesType为'scatter'。这个判断非常重要,因为一旦地图上叠了散点图,用户点在散点上,name就变成了散点的数据名,而不是地区名,你的地区逻辑会直接跑偏。
chart.on('click', function (params) { if (params.componentType !== 'geo') { return; // 只处理底图区域的点击 } const regionName = params.name; if (!regionName) return; toggleRegion(regionName); });提示:如果你的地图用的是
series-map且通过geoIndex关联到 geo,那么点击事件实际上是由 geo 组件抛出来的,componentType是'geo'。但如果你想控制点击落在哪一层,可以用silent: true关掉某一层的交互响应。
另外,params.region里通常带着这一块区域的几何信息和其他属性,做自定义 tooltip 或者反向查数据时会用到。我自己用得最多的是params.name,其他的一般直接打印出来确认。
4.2 点地图联动折线图与右侧列表
真实项目里,点地图从来不是终点,它通常是一个"筛选入口"。我给一个完整的联动骨架,包含地图点击、折线图刷新、列表高亮三件事:
const lineChart = echarts.init(document.getElementById('trend')); const listEl = document.getElementById('region-list'); function renderTrend(regionName) { const data = fetchTrendData(regionName); // 你自己的取数逻辑 lineChart.setOption({ xAxis: { type: 'category', data: data.dates }, yAxis: { type: 'value' }, series: [{ type: 'line', name: regionName, data: data.values, smooth: true }] }); } function highlightListItem(regionName) { listEl.querySelectorAll('li').forEach(li => { li.classList.toggle('active', li.dataset.region === regionName); }); } function handleRegionSelect(regionName) { selectRegion(regionName); renderTrend(regionName); highlightListItem(regionName); }反过来的方向也要打通——用户在列表里点一项,地图上对应的区域要亮起来,这就是所谓的"双向联动":
listEl.addEventListener('click', function (e) { const li = e.target.closest('li'); if (!li) return; const regionName = li.dataset.region; // 列表侧是单选语义,先把地图上旧的清掉 const prev = selectedNames.slice(); selectedNames = []; prev.forEach(unselectRegion); handleRegionSelect(regionName); });这里有个我踩过的细节:联动时一定要防止事件回环。如果地图 click 触发了列表高亮,而列表高亮又用.click()去触发地图选中,就会来回震荡。解决办法是让两条路径最终都汇聚到同一个handleRegionSelect函数上,而不是互相调用对方的点击事件。状态单向流动,问题自然消失。
4.3 再点一次取消高亮:toggle 与手动维护选中集
'single'模式下默认行为是"点新的选新的",但不一定支持"点自己取消自己"。用户的心理预期通常是点击可切换,所以我一般自己写 toggle:
function toggleRegion(name) { if (selectedNames.includes(name)) { unselectRegion(name); clearTrend(); // 取消选中时,联动区域也要跟着复位 highlightListItem(null); } else { const prev = selectedNames.slice(); selectedNames = []; prev.forEach(unselectRegion); handleRegionSelect(name); } }注意"取消选中"时联动区域的处理:折线图应该清空或者退回到"全部地区"的汇总视图,列表的高亮类名要移除。这一步很容易忘,忘记的结果就是地图上没有高亮了,但右边还显示着某个地区的曲线,用户会以为是数据错了。
5. 样式不生效的六个高频现场
5.1 高亮色被 emphasis 或 visualMap 盖掉
排在第一位的永远是状态样式冲突。ECharts 的状态优先级里,emphasis(悬停)和select(选中)都高于默认态,而当一个区域同时"被悬停"又"被选中"时,谁生效取决于版本实现,我在自己的项目里观察到的是悬停态更容易压过选中态。如果你的emphasis和select都用了相近的蓝色,用户鼠标一移开就分不清"我到底选中了没有"。
解决办法很直接:两个状态用明显不同的视觉语言。我的习惯是选中态用深色填充加粗描边,悬停态用浅色填充加轻微阴影。这样即使两个状态同时出现,用户也能看出"这块是选中的,鼠标正好在上面"。
如果你的区域填充色是由visualMap推出来的(按数值映射颜色),情况会更麻烦——视觉映射会覆盖掉默认的areaColor,而select状态下的areaColor依然生效,最终结果是选中时颜色突然跳变,跟旁边的色阶完全脱节。这时候我更推荐选中态只改描边和阴影,不改填充色,保住色阶的连续性。要让描边足够醒目,borderWidth提到 2 到 3,borderColor用高对比的深色,再配一点shadowBlur,视觉上完全够用。
5.2 选了却没变化:name 对不上
这个坑在第 2 节提过,但值得再强调一遍,因为它出现的频率实在太高。判断方法很简单:在事件回调里把params.name打印出来,和你传进dispatchAction的名字逐个字符对比。我遇到过的最隐蔽的一次,是 GeoJSON 里的名字带了一个全角空格,肉眼完全看不出来,用encodeURIComponent输出成十六进制才定位到。
批量高亮时更要注意:如果传入的数组里有一个名字对不上,其他几个照样会亮,所以你不会收到任何错误信号,只会觉得"少亮了一块"。我的做法是在highlightBatch里加一层校验,把没匹配上的名字收集起来打日志:
function highlightBatch(names, allNames) { const valid = []; const invalid = []; names.forEach(n => (allNames.includes(n) ? valid : invalid).push(n)); if (invalid.length) { console.warn('以下区域名在地图中不存在:', invalid); } // 后续只对 valid 做点亮 }5.3 roam 之后 label 打架
roam: true打开后,用户缩放地图时标签会挤成一团,尤其是小区域密集的地方。我的处理分两种情况:如果标签不是关键信息,直接按缩放比例动态开关标签;如果标签必须展示,就靠监听 roam 事件做节流控制。
let roamTimer = null; chart.on('georoam', function () { clearTimeout(roamTimer); roamTimer = setTimeout(function () { const opt = chart.getOption(); const zoom = opt.geo[0].zoom || 1; chart.setOption({ geo: { label: { show: zoom > 1.5 } } // 放大到一定程度才显示标签 }); }, 120); });这里用setTimeout做节流是必要的。georoam在拖动过程中会高频触发,如果每次都setOption,地图会明显卡顿,严重的还会出现拖动跟手性变差、松手后位置回弹。
5.4 框架里 setOption 把选中态洗掉了
在组件化框架里,数据更新往往伴随着重新setOption。如果你在重新设置时用了整体替换的写法,选中态会被一起清空——因为对 ECharts 来说,这就是一份全新的配置。
规避方式有两种。一种是保持合并更新,只把变化的部分传进setOption,别整份option重设;另一种更保险:在确有整体重设需要的场景下,setOption之后重新派发一次当前选中集,把状态补回去。
function refreshOption(newOption) { const keep = selectedNames.slice(); chart.setOption(newOption); keep.forEach(name => { chart.dispatchAction({ type: 'geoSelect', geoIndex: 0, name: name }); }); }5.5 双地图叠加导致点击事件被抢
如果同时存在 geo 和 series-map,且没做去重,你会看到两层地图重叠在一起——通常表现为边界线颜色偏深、或者悬停时出现双重高亮。这时候点击事件由谁抛出就变得不确定了。排查方法是在 click 回调里无条件打印params.componentType和params.seriesType,一眼就能看出点到了哪一层。
正确的做法是选一条路:要么用series-map并设置geoIndex让它复用 geo 坐标系(此时它不参与渲染),要么完全不写 series-map。不要两个都渲染。
5.6 select 状态在旧版本上不存在
geo.select这个状态样式配置是较新版本才有的能力,早期版本里 geo 只有itemStyle和emphasis。如果你的项目锁定了老版本,那select配了也不生效,而且不会有任何提示。判断方式很简单:console.log(echarts.version)看一眼版本号,再去对照你能用的能力集合。老版本上的替代方案就是第 3.2 节的regions全量重绘,功能上完全能覆盖,只是要多写几行代码。
6. 数据量上来之后的性能取舍
6.1 频繁 dispatchAction 的成本
单次dispatchAction的开销其实不大,它主要触发的是 geo 组件的局部重绘,不会重建整个图表。但它不是免费的,尤其是在地图区域多、样式复杂(带阴影、渐变、纹理)的时候。我做过一个粗略的体感测试:在一张区域数接近三百的底图上,连续派发几十次geoSelect,如果中间没有做批处理,页面会有肉眼可见的卡顿。
所以有两条经验可以直接用。第一,批量高亮时先清后设,而不是边清边设,能减少中间态的无效重绘次数。第二,绝对不要在mousemove或者georoam里派发选中 action,那会把重绘频率拉到几十赫兹,再好的机器也顶不住。
还有一点容易被忽略:如果高亮逻辑依赖后端接口返回的数据,要加上请求的取消或序号校验。用户快速点击多个区域时,先发的请求可能后返回,导致地图上高亮的区域和右边图表显示的数据对不上。我一般用一个自增的请求序号来丢弃过期响应:
let reqSeq = 0; function handleRegionSelect(regionName) { const seq = ++reqSeq; selectRegion(regionName); fetchTrendData(regionName).then(data => { if (seq !== reqSeq) return; // 已经过期,丢弃 renderTrendChart(data); }); }6.2 省市区三级下钻的加载与缓存策略
地图应用做深了必然要下钻。我的经验是:不要一开始就把三级地图全量注册进来,那份数据体积很大,会明显拖慢首屏。按需加载才是正解,加载完的用 Map 缓存住,避免来回切换时反复请求。
const mapCache = new Map(); async function loadAndRegister(adcode, mapName) { if (mapCache.has(mapName)) { echarts.registerMap(mapName, mapCache.get(mapName)); return; } const res = await fetch(`/geo/${adcode}.json`); const geoJson = await res.json(); mapCache.set(mapName, geoJson); echarts.registerMap(mapName, geoJson); } // 下钻时 async function drillDown(adcode, mapName) { await loadAndRegister(adcode, mapName); chart.setOption({ geo: { map: mapName, regions: [], selectedMode: 'single' } }, false); selectedNames = []; // 换图后清空选中集,否则旧区域名会残留 }下钻这里有两个必做的动作。一是换图后清空selectedNames,因为上一级的地名在新地图里不存在,不清的话你后续的"取消选中"会派发一堆无效 action。二是给regions传空数组并保持合并模式,让新地图用默认样式渲染,避免上一层的染色残留。
另外,如果下钻后还想保留"从哪来的"面包屑导航,可以把路径信息存在外部状态里,会话级别记住,别塞进 ECharts 的配置对象。图表配置只负责画图,业务状态归业务状态,混在一起后面维护会非常痛苦。
我个人在实际项目里的做法是:地图只暴露两个接口——highlight(names)和clear(),所有外部联动都走这两个入口,内部是 action 还是 regions 重绘完全不对外暴露。这样做的好处是,将来换方案(比如从geoSelect改成regions)时,只需要改这两个函数的实现,几十个调用点一行都不用动。地图交互的复杂度必须被收敛到一个小口子里,否则它迟早会变成整个页面里最难维护的那一块。