今天学生遇到一个问题,后台接口获得的数据在生成列表时,el-input绑定v-model的数据虽然能显示,但是无法输入和修改数据。
分析原因是在定义data数据初始化时候没有把原始的属性结构定义,AJAX返回结果后直接给form添加新的属性并赋值,虽然数据能显示,但是这样添加的属性不是响应式的,如果在模板或渲染函数中使用该属性,这个新的属性将无法触发组件重新渲染。
例如
export default {
// 省略其他代码 ...
data() {
return {
form:{
// 没有定义checkList结构
}
};
},
methods: {
getList() {
list().then(resp=>{
// 此处resp.data是一个对象数组,其中数组中的一个对象某个属性绑定el-input的v-model
// 结果就是只能看不能改
this.form.checkList = resp.data;
})
},
}
// 省略其他代码 ...
};
解决方案:
网上有一些方案,比如添加input、change之类的事件,调用强制更新this.$forceUpdate(),试过之后并没有解决问题,分析是因为本来el-input就已经是无法修改状态,input,change事件怎么触发呢?现在想想keydown倒是没试过,不知道有没有用...
<el-input @input="forceUpdate" v-model="xxx"></el-input>
...
methods:{
forceUpdate(){
this.$forceUpdate()
}
}
还有什么给el-input外层的template加slot-scope,没有解决问题。
<template slot-scope="scope">
如果是非对象或数组的属性,可以用this.$set来设置数据,由于data是数组,还是无效的
this.$set(this.form, "checkList", resp.data);
最后想了想,自己写了一个递归深拷贝的方法,成功解决问题。代码应该还能再简化简化,前端能力有限没再整了,贴出代码供参考,欢迎前端大佬指教和优化
export default {
// 省略其他代码 ...
data() {
return {
form: {}
};
},
methods: {
getList() {
list().then(resp => {
// 此处resp.data是一个对象数组,其中一个属性绑定el-input的v-model
// 结果只能看不能改
// this.form.checkList = resp.data;
// 改为调用深拷贝方法
this.setDataTo(this.form, "checkList", resp.data);
})
},
/**
* Vue数据深拷贝
* @param obj 被设置数据的对象
* @param member 被复制添加到obj中的属性名
* @param data 需被深拷贝到obj中的属性值
*/
setDataTo(obj, member, data) {
if (data == null) {
return;
}
obj[member] = data;
if (data instanceof Array) { // 处理数组
if (!obj[member]) {
// 数据是数组,就设置为数组,元素循环设置
obj[member] = [];
}
for (let index in data) {
// 递归设置每个数组元素
this.setDataTo(obj[member], index, data[index]);
}
} else if (typeof data == "object") { // 处理对象
if (!obj[member]) {
// 数据是对象,就设置为对象,属性循环设置
obj[member] = {};
}
for (let [key, value] of Object.entries(data)) {
// 递归设置每个属性
this.setDataTo(obj[member], key, value)
}
} else {
// 其他类型的普通字段直接设置
this.$set(obj, member, data);
}
}
}
// 省略其他代码 ...
};
这个方法的好处在于能动态的设置响应式数据,也许有更优雅的方式、也可能我只是在重复造轮子,但是解决了问题也是一种收获。