Cascader级联选择组件深度实践与常见问题解决方案
为啥要搞这么复杂的对比?
最近项目里遇到一个需求,让用户选择省市县三级联动,本来以为很简单,结果发现市面上各种Cascader组件方案五花八门。Element UI的、Ant Design的、还有原生自己写的,每个都有各自的玩法。之前踩过几个坑,这次决定好好梳理一下,免得以后再踩。
说实话,Cascader这个东西看着简单,但实际用起来坑还真不少。数据结构要适配、异步加载要考虑、搜索功能还要支持,一不小心就掉坑里了。
Element UI Cascader:稳妥之选
我比较喜欢用Element UI的Cascader,主要是因为它够成熟,社区支持也到位。代码写起来相对简单:
<template>
<el-cascader
v-model="selectedValue"
:options="options"
:props="{
value: 'id',
label: 'name',
children: 'children',
lazy: true,
lazyLoad: loadOptions
}"
placeholder="请选择地区"
filterable
/>
</template>
<script>
export default {
data() {
return {
selectedValue: [],
options: [
{
id: 1,
name: '北京市',
children: [
{
id: 101,
name: '东城区'
}
]
}
]
}
},
methods: {
async loadOptions(node, resolve) {
// 异步加载子选项
const response = await fetch(https://jztheme.com/api/area/${node.value})
const data = await response.json()
resolve(data.map(item => ({
id: item.id,
name: item.name,
leaf: item.level === 3
})))
}
}
}
</script>
Element UI的Cascader有个好处就是配置项比较丰富,支持懒加载、可搜索、多选这些常用的交互。但问题是数据结构有点固定,如果你的后端返回的数据格式不匹配,还得先转换一遍。
Ant Design Cascader:功能更强
Ant Design的Cascader在功能上确实更强大一些,特别是那个动态加载的支持:
import { Cascader } from 'antd';
const App = () => {
const [options, setOptions] = useState([]);
const loadData = (selectedOptions) => {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
fetch(https://jztheme.com/api/area/${targetOption.value})
.then(response => response.json())
.then(data => {
targetOption.loading = false;
targetOption.children = data.map(item => ({
label: item.name,
value: item.id,
isLeaf: item.level === 3
}));
setOptions([...options]);
});
};
return (
<Cascader
options={options}
loadData={loadData}
changeOnSelect
placeholder="请选择地区"
showSearch={{
filter: (inputValue, path) =>
path.some(option =>
option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1
)
}}
/>
);
};
Ant Design的优势在于灵活性更高,特别是那个changeOnSelect属性,可以让用户在每一级都能触发onChange事件。搜索功能也做得比较完善,支持路径搜索。
但这里我踩过一个坑:Ant Design的Cascader对于数据结构的要求更严格,label和value字段不能变,想换个字段名都不行,只能在数据拿到后先处理一遍。
原生手写:完全可控
有时候遇到特别定制的需求,三方库的Cascader不够用,那就得自己写了。虽然麻烦点,但控制力最强:
// 数据处理
function buildTree(data, level = 1) {
return data.filter(item => item.level === level).map(item => ({
...item,
children: buildTree(data, level + 1).filter(child => child.parentId === item.id)
}));
}
// 组件实现
export default {
data() {
return {
panels: [[], [], []],
selected: []
}
},
mounted() {
this.loadAreaData();
},
methods: {
async loadAreaData(level = 1, parentId = null) {
const params = parentId ? ?parentId=${parentId} : ?level=${level};
const response = await fetch(https://jztheme.com/api/area${params});
const data = await response.json();
if (level <= 3) {
this.$set(this.panels, level - 1, data);
}
},
onPanelChange(level, item) {
this.selected[level] = item;
this.selected.splice(level + 1); // 清空后面的选择
if (level < 2) { // 最多三级
this.loadAreaData(level + 2, item.id);
}
}
}
}
手写的好处就是完全按照你的业务逻辑来,想要什么功能自己加。比如我现在做的项目里,需要在第二级选择后立即触发某些业务逻辑,这种情况三方库都很难支持,只能自己实现。
但缺点也很明显:工作量大,要考虑的边界情况多,而且还要自己处理动画效果、键盘导航这些用户体验相关的细节。
谁更灵活?谁更省事?
从我的实际使用经验来看,如果是一般的业务需求,Element UI是最省事的,配置项够用,文档也比较清晰。遇到复杂一点的交互逻辑,Ant Design会更灵活一些。
但如果真的遇到特别定制的需求,比如需要在每一级显示额外的信息、特殊的交互逻辑等等,还是得考虑自己写。虽然费劲,但能完全掌控。
性能方面其实差别不大,都是按需加载,主要是数据处理这块需要注意。我之前在一个项目里没处理好数据缓存,每次打开都重新请求,用户体验就很差。
我的选型逻辑
现在我一般是这样选的:
- 简单的需求,Element UI就够了,开发速度快
- 需要特殊交互或者功能要求比较多,优先考虑Ant Design
- 特别定制化的需求,或者是性能要求很高的场景,考虑手写
还有一个考虑因素就是团队成员的熟悉程度。如果大家都用惯了Element UI,就没必要为了一个小功能切换到别的库。
数据结构的适配也是个头疼的问题。我个人建议在项目初期就把后端返回的数据格式统一规范一下,避免前端做过多的数据转换工作。
踩坑提醒:几个常见陷阱
用了这么久的Cascader,踩过几个印象深刻的坑:
首先是懒加载的时候,如果异步请求失败了,一定要给用户明确的错误提示,不然界面看起来正常,实际上数据加载不出来,用户体验很糟糕。
还有就是搜索功能,有些用户会输入很奇怪的字符,记得做好防抖和异常处理,别因为用户的输入导致页面崩溃。
最后一个就是移动端兼容性,某些老版本的移动端浏览器对Cascader的滚动支持不太友好,需要额外处理。
以上是我个人对这个Cascader组件的完整讲解,有更优的实现方式欢迎评论区交流。

暂无评论