问题描述
我们常常在表单中会遇到联动下拉框的需求,最常见的例子是省、市、区的联动。一般联动的特点在于:
-
前一级变化时,后面所有层级全部清空:包括下拉框本身的值和下拉框的枚举
-
后一级的枚举会带着前一级的某个参数(如id)查询接口
如果只是一个新增表单上面两个注意点在编码过程中,稍加注意即可,用一个 watch
监听事件就可以很好的实现联动。下面我们将以省、市、区的案例简单整理下逻辑。
省、市、区常规案例
下面以vue+伪代码的形式大致梳理下整体思路
定义变量:
// 在vue3中的代码,可以参考思路
const form = reactive({
province: '', // 省
city: '', // 市
area: '', // 区
});
// 枚举
const provinceEnums = ref([]);
const cityEnums = ref([]);
const areaEnums = ref([]);
监听省变化:
watch(
() => form.province,
(val) => {
// 1. 清空市
form.city = '';
// 2. 如果val有值(选择了新的),根据新选的省查询市的枚举,如果val没有值,则是清空操作,则清空市的枚举
if (val) {
// 重新获取市的枚举
// todo
} else {
// 清空市的枚举
cityEnums.value = [];
}
}
)
监听市变化:
watch(
() => form.city,
(val) => {
// 1. 清空区
form.area = '';
// 2. 如果val有值(选择了新的),根据新选的市查询区的枚举,如果val没有值,则是清空操作,则清空区的枚举
if (val) {
// 重新获取区的枚举
// todo
} else {
// 清空区的枚举
areaEnums.value = [];
}
}
)
问题描述
如果我们采用了上面的方案,在做新增的时候是没有问题的,但如果我们有重新编辑表单的操作的时候就会出问题了。问题的主要原因在于异步。
我们试想一下,还是上面的逻辑,编辑较新增不同的地方在于,表单是有初始值的,这一份数据来自于上一次保存的数据。所以我们的代码会多一步:获取详情数据并赋值
// 获取详情数据并赋值
const getDetailData = async () => {
// 1. 请求接口
let data = await getDetail();
// 2. 赋值
form.province = data.province;
form.city = data.city;
form.area = data.area;
}
在实际效果中就可能会出现问题。问题主要是因为 : 使用了watch
。我们来梳理这个逻辑:
-
给
form.province
赋值 -
给
form.city
赋值 -
给
form.area
赋值 -
触发
city.watch
监听,并在这个时候 清空了form.area
-
触发
province.watch
,并在这个时候 清空了form.city
下面是控制台输出的顺序:
体现在我们页面上的效果是:只有省有值,市、区的值因 watch执行顺序慢于赋值而被清空
当然我们可以在watch中添加一些限定条件,比如判断是否是详情进入的,比如在监听省的时候,通过判断是否有 oldVal
,来做一些判断,这里就不展开介绍了。
watch(
() => form.province,
(val, olaVal) => {
if (oldVal) {
// todo
}
}
新的思路 onChange
对于这种问题,最后想到的解决方案是,放弃watch
监听,因为它的监听模式是自动开启的,只有有变化就会按照固有代码执行,而我们需要的是会根据:
-
编辑模式第一次进入状态
-
常规切换状态
来控制代码的逻辑。所以我们不用watch,而用输入框自带的 onChange
事件。
这里做一下简单的区别:
-
watch:只要vModel的值变化就会监听到
-
onChange:通过触发输入框改变才会触发事件本身
这就意味着,我们在获取到详情数据赋值时,是不会触发onChange事件的,那这个里面的逻辑就全部由我们自己定制。
获取详情并赋值
// 获取详情数据并赋值
const getDetailData = async () => {
// 1. 请求接口
let data = await getDetail();
// 2. 赋值
form.province = data.province;
// 获取市枚举
cityEums.value = await getCityByProvince(form.province);
// 更新市
form.city = data.city;
// 获取区枚举
cityEums.value = await getAreaByCity(form.area);
// 更新区
form.area = data.area;
}
总结
目前遇到这一类问题,我主要用 onChange
代替 watch
。如果在比较复杂的业务场景下,一定要用watch来实现的话,可能代码会比较复杂,这个时候不妨考虑下 onChange
。
以上是本人个人观点,有更好的处理方式可以一起分享。