备注:细节可能有误,主要提供思路
接着上一篇,这篇主要讲解字段监听和控制相关内容。
设计模式
在这之前,需我们要对观察者模式和发布订阅模式有一定的认识。以下两张图直观的的描述了二者的区别。这里需要深入了解的话,移步度娘,相关的文章很多。
![](https://img-blog.csdnimg.cn/6cf99b11e8004ee5b6414f8665f526bf.png)
![](https://img-blog.csdnimg.cn/0c2fd4cc6e804beda197bce1ae507701.png)
字段的监听
目的:在业务代码层,实时监听某个字段内容的改变。
实现方案:在模板解析的完成后,为需要监听的字段注册一个回调函数,并将该字段信息及回调函数收集起来,全部分发到底层的表单单元上。在底层表单单元渲染时,判断自身是否在收集的信息中,若存在,当值改变时,调用该回调函数。
首先,我们需要注册一个全局变量,注入到每个分组和表单单元中。
修改form-format.js
// 新增函数,用于获取字段的索引,如["formData", "basic", "name"]
function setIndex(parents = [], key = 'formData') {
const index = [...parents];
index.push(key);
return index;
}
function format(list = []) {
if (!Array.isArray(list)) {
return [];
}
const rootIndex = setIndex();
const $listen = {
$actionPublish: {}, // 动态响应的发布,这个用于收集监听字段时注册的字段及函数
};
const tplList = list.map((i) => {
// const groupItem = { ...i };
// 把索引和$listen一起放进来
const groupItem = {
...i,
$index: setIndex(rootIndex, i.groupNo),
$listen,
};
...
});
// 同理修改handleFormFieldList
function handleFormFieldList() {
// const fieldItem = { ...o };
const fieldItem = {
...o,
$index: setIndex(groupItem.$index, o.fieldId),
$listen,
};
}
在动态表单入口Index.vue中,添加以下代码
formatTpls() {
const { $listen, tplList } = formFormat.call(this, this.formConfig);
this.$listen = $listen;
this.tplList = tplList;
// actionPublishConfig是业务代码层传下来的培配置
// 这里先默认为
// [{
// fieldKey: "formData.basic.name",
// cb: this.handleNameChange // 业务代码中的函数
// }]
this.actionPublishConfig.forEach(item => {
this.registerActionPublish(item.fieldKey, (val) => {
item.cb(val);
return true;
});
});
}
/**
* 新增该函数
* 注册监听动态发布
*/
registerActionPublish(key, cb) {
if (!this.$listen) {
return;
}
const { $actionPublish } = this.$listen;
if (!$actionPublish[key]) {
$actionPublish[key] = [];
}
$actionPublish[key].push(cb);
},
到这里,已经把监听的字段及回调注册到每一个表单单元中了,接下来修改表单单元,打开FormItem.vue,添加以下代码:
mounted() {
this.init();
// 新增initWatchValue
this.initWatchValue();
},
watch: {
inpValue: {
handler(val) {
// 新增hanldeInpValueChange
this.hanldeInpValueChange();
this.$emit("input", val);
},
immediate: true,
},
},
methods: {
init() {
...
},
initWatchValue() {
const { $index, $listen } = this.item;
const currentFieldIndex = $index.join(".");
if (!$listen.$actionPublish[currentFieldIndex]) {
this.$actionPublish = null;
return;
}
this.$actionPublish = $listen.$actionPublish[currentFieldIndex];
},
hanldeInpValueChange() {
if (Array.isArray(this.$actionPublish)) {
this.$actionPublish.forEach(() => {
f(this.inpValue, this);
})
}
}
}
这样就实现了字段内容改变的实时监听,避免了深度watch的开销。
字段状态的控制
这里主要用到发布订阅的设计模式。
修改表单的配置信息,增加控制的逻辑配置,以第一篇中的配置为基准,添加control的信息:
{
component: "el-select",
fieldId: "country",
name: "国家",
...
control: [
{
value: "CN",
fields: ["formData.basic.name"]
},
{
value: "US",
fields: ["formData.basic.name"]
},
{
value: "US",
fields: ["formData.basic.sex"]
}
]
}
修改form-format.js,添加发布者、订阅者:
function format(list = []) {
...
const $listen = {
$publish: {}, // 发布者
$subscribe: {}, // 订阅者
$actionPublish: {}, // 动态响应的发布
};
// 调用handleFormFieldList时,传入$listen
const {
fields,
formData,
rules,
} = handleFormFieldList.call(this, i.fields, groupItem, $listen);
...
}
function handleFormFieldList(originFields, groupItem, $listen) {
...
const fields = originFields.map((o) => {
...
// 添加以下代码
if (o.control) {
// 初始化相应的发布者
$listen.$publish[o.fieldId] = [];
o.control.forEach((i) => {
const { fields, value } = i;
Array.isArray(fields) && fields.forEach(field => {
// 初始化相应的订阅者
if (!$listen.$subscribe[field]) {
$listen.$subscribe[field] = [];
}
$listen.$subscribe[field].push({
...o,
targetVal: value, // 目标值
});
});
});
// 为每个单元注册一个函数,用于通知订阅者执行相关函数
fieldItem.$$watchValue = (value) => {
const fieldId = o.fieldId;
if ($listen.$publish[fieldId]) {
const map = {};
$listen.$publish[fieldId].forEach(({ fn, index }) => {
// fn(value) 的返回值是对应组件的editStatus
// 当发布者中的订阅函数未执行,或执行结果为true时,继续执行该其他订阅
// 即:当执行到为false时,不执行其余函数
// 目的是为了解决多个字段控制同一个字段时的互斥问题
if (map[index] === undefined || map[index] === true) {
map[index] = fn(value);
}
});
}
}):
}
});
}
ok,准备工作完成,下面回到FormItem.vue中,修改如下:
watch: {
inpValue: {
handler(val) {
// 新增
// 当改变值时,调用form-format中注册的函数,通知相应的发布者执行订阅
if (this.item.$$watchValue) {
this.item.$$watchValue(this.inpValue);
}
...
},
immediate: true,
},
},
mounted() {
this.init();
// 新增handleSubscribe
this.handleSubscribe();
this.initWatchValue();
},
// 新增
beforeDestroy() {
this.removePublish();
},
methods: {
/**
* 处理订阅
*/
handleSubscribe() {
const { $index, $listen, fieldId } = this.item;
const index = $index.join(".");
const subscribe = $listen.$subscribe[index];
if (!subscribe) {
return;
}
// 当前字段的值,控制其他字段不可编辑
subscribe.forEach((i) => {
let publisher = $listen.$publish[i.fieldId];
const fn = (value) => {
if (Array.isArray(value)) {
// 多选的处理
this.editStatus = !value.includes(i.targetVal);
} else {
this.editStatus = !(i.targetVal === value);
}
return this.editStatus;
};
// 将发布者和订阅者关联
publisher && publisher.push({ fn, index });
});
},
/**
* 从发布者中移除订阅
*/
removePublish() {
const { $listen, fieldId } = this.item;
const { $publish } = $listen;
const key = `${fieldId}`;
if ($publish[key]) {
delete $publish[key];
}
},
}
到此,字段的监听和控制基本完成。下一篇进行表单模式的完善。