之前写过一篇文章漫谈 vue 组件设计 ,这里写这篇文章原因是不满一个相同的组件需要在各个页面重复写,而且这样搞维护成本实在太高,但是受限于 vue2.x 的响应机制没有想到更好的方法,所以在某些组件中使用的时候(例如在表单中使用需要添加校验规则、按需使用组件)需求很难实现。
而从 Vue3.0 的响应式的重写以及组合 api 的出现,让我觉得眼前一亮,下面以省、市、区级联选择和表单校验为例子,因为主要
讲解思路所以 UI 方面选择了ant-design-vue帮助快速构建视图
准备实现功能

先上一张最终实现的效果图,下面内容会对照着讲解,代码放置到了github.com/bosens-Chin…
- 省、市、区组件可以自由组合使用(但是这个例子选取的不太好关系相互之间依赖,所以实际上只能
省+市+区、省+市) - 不需要手动管理状态,只需要提供一个
v-model作为绑定值即可
实现
根据上面需求,我们一个个实现
- 组件组合使用
要实现组件组合使用,这就要求组件本身耦合度不能太高,只展示数据并不进行任何的省、市、区具体功能的处理
<a-select value={props.modelValue} onChange={onChange}>
{...modelValueList.value.map((item) => {
return <a-select-option value={item.name}>{item.value}</a-select-option>;
})}
</a-select>
组件的结构如上所示,其实省、市、区的组件结构是一样的不同的只是结构不同,所以实现的时候只需要使用工厂模式即可,这样可以做到少维护可扩展。
- 管理更新状态
这一点使用了组合 api 来完成,在新发布的版本中,vue 把响应式功能抽离出来,响应式数据可以通过ref来定义,通过watch来做到监听,例如这一个例子
import { watch, ref } from "vue";
const test = ref(null);
watch(test, () => {
console.log("变更");
});
test.value = 5;
而且这个 ref 可以共享给其他程序使用,例如通过import导入给其他组件使用,这样刷新的时候自然而然组件也会发生变化。
下面通过一个rely.js文件来进行组件状态管理,它可以帮助我们实现以下流程

在实际过程中我们切换省、市、区的时候需要我们还可以判断下绑定值存不存在 list 数据中,不存在就要清空。
下面通过注释的方式来讲解
import { ref, watch } from "vue";
import { getProvince, getCity, getArea } from "../../api/region";
const created = () => {
// 下面字段分比为省、市、区的绑定字段以及list数据
const province = ref(undefined);
const provinceList = ref([]);
const city = ref(undefined);
const cityList = ref([]);
const area = ref(undefined);
const areaList = ref([]);
// 初始化省级列表
getProvince()
.then((res) => {
provinceList.value = res;
})
.catch(() => {
provinceList.value = [];
});
// 监听省级变化,更新市级的变化
watch(province, (value) => {
if (!value) {
city.value = undefined;
cityList.value = [];
return;
}
getCity(value)
.then((res) => {
cityList.value = res;
// 如果绑定值不存在对应的list数据中清除掉
city.value = res.find((f) => f.name === city.value)?.name;
})
.catch(() => {
cityList.value = [];
city.value = undefined;
});
});
// 监听市级变化,更新区的数据
watch(city, (value) => {
if (!value) {
areaList.value = [];
area.value = undefined;
return;
}
getArea(value)
.then((res) => {
areaList.value = res;
// 如果绑定值不存在对应的list数据中清除掉
area.value = res.find((f) => f.name === area.value)?.name;
})
.catch(() => {
areaList.value = [];
area.value = undefined;
});
});
// 返回数据,提供给其他组件使用
return {
province,
provinceList,
city,
cityList,
area,
areaList,
};
};
export default created;
这里需要返回一个函数,因为组件状态不能被污染
- 组合
assembly.js
import { watch } from "vue";
import created from "./rely";
const basicsComponents = () => {
// 数据
const { province, provinceList, area, areaList, city, cityList } = created();
// 工厂模式返回视图
const assemble = (modelValue, modelValueList) => {
return {
// 3.0中默认绑定的字段
props: {
modelValue: null,
},
setup(props, { emit }) {
// 监听绑定值变化
watch(modelValue, (value) => {
emit("update:modelValue", value);
});
// 监听modelValue值,更新到v-model中
watch(
() => props.modelValue,
(value) => {
modelValue.value = value;
},
{ immediate: true }
);
const onChange = (value) => {
emit("update:modelValue", value);
};
return () => (
<a-select value={props.modelValue} onChange={onChange}>
{...modelValueList.value.map((item) => {
return (
<a-select-option value={item.name}>
{item.value}
</a-select-option>
);
})}
</a-select>
);
},
};
};
const componentsProvince = assemble(province, provinceList);
const componentsCity = assemble(city, cityList);
const componentsArea = assemble(area, areaList);
return {
componentsProvince,
componentsCity,
componentsArea,
};
};
export default basicsComponents;
最后
在使用过程中发现 Vue3.0 的热更新速度提升幅度不小使用起来很丝滑,但是还是有不少问题,比如我对 css 添加了scoped在开发环境下没有任何问题,在生产环境下data-***属性没有出现导致样式没作用起来,所以生产环境使用需要谨慎。
文章如果对你有帮助,可以点一下star支持一下作者。