问题
1.使用avue form表单,avue form表单当使用axios,fetch,ajax等异步获取数据时,在mount中给model赋值,数据是改变了,但视图可能不更新显示
2.Vue 异步请求导致页面数据渲染错误
相关浏览器:firefox(新版,我的是114.0 (64 位))出现机率大,edge和chrome偶尔出现
一、问题分析
经过页面数据输出及debugger断点调试,发现在页面渲染结束前,异步数据并未处理完毕,导致页面数据渲染问题。
当vue实例生成后,再次给对象赋值时,并不会自动更新到视图上去; 当我们去看vue文档的时候,会发现有这么一句话:如果在实例创建之后添加新的属性到实例上,它不会触发视图更新。
受 ES5 限制,Vue.js 不能检测到对象属性的添加或删除,即Vue未做到脏数据检查。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。
二、解决措施
已尝试的方法1:通过this.$set()方法处理,要对控件用的prop值set才会有用。
1.有效的this.$set(),这对大对象赋值将是麻烦事,不如用v-if方便
this.$set(this.model, "account", data.account);
this.$set(this.model, "address", data.address);
2.无效的this.$set();
this.$set(this, "model", data);//无效,并不会重新渲染
已尝试的方法2:通过this.$nextTick(方法处理,但这个方法在我的firefox中偶尔也会出问题。
this.$nextTick(function () {
this.$axios
.get(url, {
responseType: "json",
})
.then((res) => {
if (res.status == 200) {
let data = JSON.parse(JSON.stringify(res.data.data));
this.model = data;
}
});
});
终级且超级简单的方法,用Object.assign()给data对象属性赋值(使用修改栈能触发视图更新的特性)。
this.$axios
.get(url, {
responseType: "json",
})
.then((res) => {
if (res.status == 200) {
let data = JSON.parse(JSON.stringify(res.data.data));
// this.model = data; //不用这个
this.model = Object.assign(this.model, data);
}
});
相对方便的有效方法:可通过v-if控制页面渲染、销毁逻辑,在异步方法请求前销毁相应数据,异步方法请求成功后新建相应数据段,这样用this.model = data赋值,相对方便。
三、相关代码(详见DialogBindDataComponent.vue)
1.页面AvueDialogBindData.vue
title="绑定对象赋值测试" v-dialogDrag :close-on-click-modal="false" :close-on-press-escape="false" :before-close="beforeColoseDialog" :visible.sync="dialogFlag" class="avue-dialog myclass my-dialog" width="650px" top="10px" > :is="components" :param="param" :key="new Date().getTime()" @close="closeDialog" />
import { AvueDialogBindDataOption } from "@/options/AvueDialogBindDataOption.js";
import DialogBindDataComponent from "@/components/DialogBindDataComponent.vue";
export default {
name: "DialogForm",
components: { DialogBindDataComponent },
provide() {
return {
param: this.param,
closeDialog: this.closeDialog,
confirmClose: this.confirmClose,
};
},
data() {
return {
components: "",
param: {},
dialogFlag: false,
model: {},
};
},
computed: {
avueDialogFormTableOption() {
return AvueDialogBindDataOption(this, this.model); //表单项
},
},
mounted() {
this.getData();
},
methods: {
getData() {
this.model = "";
setTimeout(() => {
this.model = { account: "账号值" + Math.random(), address: "地址值" };
}, 3000);
},
showDialog() {
this.dialogFlag = true;
this.param.model = { account: "account from parent" };
this.param.statusName = "新增";
this.components = "DialogBindDataComponent";
},
// 关闭前调用,右上角关闭时可调用
beforeColoseDialog(done) {
this.components = false;
done();
// this.baseQuery();
},
// 弹框关闭调用
closeDialog() {
this.beforeColoseDialog(() => {
this.dialogFlag = false;
});
},
confirmClose() {
this.$confirm("此操作将关闭弹框, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
this.dialogFlag = false;
})
.catch(() => {});
},
},
};
.myclass .el-dialog__body {
margin-bottom: 0px !important;
}
.my-dialog {
.el-col-sm-12 {
width: 100% !important;
}
}
2.option文件 AvueDialogBindDataOption.js
/*
* @Description:
* @Version: 1.0
* @Autor: Tj
* @Date: 2023-03-21 11:02:42
*/
export const AvueDialogBindDataOption = (vueObj) => {
return {
labelWidth: 100, //整体列标签宽度
submitText: "提交",
span: 24,
size: "small", //medium/small/mini
emptyBtn: false,
menuPosition: "right",
column: {
account: {
label: "账号",
prop: "account",
},
address: {
label: "地址",
prop: "address",
type: "textarea",
minRows: 2,
maxRows: 3,
labelTip: "地址必填",
labelTipPlacement: "bottom-start",
rules: [
{
required: true,
message: "请选择收货地址",
trigger: "blur",
},
],
},
},
};
};
3.组件文件DialogBindDataComponent.vue
v-if="showForm" ref="dialogForm" :option="avueDialogFormTableOption" :defaults.sync="defaults" v-model="model" @submit="handleSubmit" >
import { AvueDialogBindDataOption } from "@/options/AvueDialogBindDataOption.js";
export default {
name: "DialogFormData",
components: {},
inject: ["param", "closeDialog", "confirmClose"],
data() {
return {
showForm: false,
dialogFlag: false,
defaults: {},
model: { account: "", address: "" },
};
},
computed: {
avueDialogFormTableOption() {
return AvueDialogBindDataOption(this); //表单项
},
},
mounted() {
this.getData();
},
methods: {
getData() {
showForm: false, //异步调用前让表单销毁
let url = "/api/vue_data.php?" + Math.random();
this.$axios
.get(url, {
responseType: "json",
// responseType: "arrayBuffer",
})
.then((res) => {
if (res.status == 200) {
let data = JSON.parse(JSON.stringify(res.data.data));
this.model = data;
// this.$set(this.model, "account", data.account);
// this.$set(this.model, "address", data.address);
showForm: true,//赋值完成后,加载表单,开始渲染
}
});
},
showDialog() {
this.dialogFlag = true;
},
handleSubmit(form, done) {
this.$message.success("3s后关闭");
console.log(form);
setTimeout(() => {
done();
}, 3000);
},
},
};
.myclass .el-dialog__body {
margin-bottom: 0px !important;
}
.my-dialog {
.el-col-sm-12 {
width: 100% !important;
}
}
总结:
在同一组件中,使用多个avue-form或avue-crud时,v-model绑定的对象不能用同一个,否则部分视图更新可能会失败加载时推荐使用Object.assign()和this.$set()给model赋值用Object.assign()赋值时不能用Object.assign({}, data),只能用Object.assign(this.model, data)Object.assign()赋值后用this.$set()赋值,不能用“=”给model赋值,如this.model.member={…}this.$set()前后都不能用“=”给model赋值
文章链接
发表评论