reactive数据修改无效

server/2025/3/19 9:56:44/

环境

vue:3.2.13

element-plus: 2.9.6

typescript:4.5.5

问题

表格列表页面,页面中有新增和修改操作,新增和修改共用一个弹窗,弹窗中表单绑定的数据修改无效。复现步骤是先点击表格中的修改,然后点击新增,此时弹窗中表单数据没有置空(有赋值为空的操作)。

问题代码

代码只是为了复现问题,有类型标注缺失或者其他小问题

<template><div class="home"><el-form :inline="true" :model="dataForm" @keyup.enter="getDataList()"><el-form-item><el-input v-model="dataForm.name" placeholder="名称" clearable></el-input></el-form-item><el-form-item><el-button @click="getDataList()">查询</el-button></el-form-item><el-form-item><el-button type="primary" @click="addOrUpdateHandle()">新增</el-button></el-form-item></el-form><el-table v-loading="loading" :data="dataList" border style="width: 100%"><el-table-column prop="name" label="名称" header-align="center" align="center"></el-table-column><el-table-column prop="createDate" label="创建时间" show-overflow-tooltip header-align="center" align="center"></el-table-column><el-table-column prop="creatorName" label="创建者" header-align="center" align="center"></el-table-column><el-table-column label="操作" fixed="right" header-align="center" align="center" width="248"><template v-slot="scope"><el-button type="primary" link @click="addOrUpdateHandle(scope.row)">修改</el-button></template></el-table-column></el-table><add-or-update ref="addOrUpdateRef" @refreshDataList="getDataList"></add-or-update></div>
</template><script lang="ts" setup>
import { ref, onMounted } from 'vue';
import AddOrUpdate from './add-or-update.vue';
const dataForm = ref({name: '',
});
const dataList = ref([]);
const loading = ref(false);
// 模拟接口数据
function generate() {const arr = [];for (let index = 0; index < 8; index++) {arr.push({id: index + 1,name: `name${index}`,createDate: `2025-3-${index + 1}`,creatorName: 'admin',});}return arr;
}
// 模拟接口请求
const getDataList = () => {loading.value = true;setTimeout(() => {dataList.value = generate();loading.value = false;}, 1500);
};
onMounted(() => {getDataList();
});// 新增或者修改弹窗
const addOrUpdateRef = ref();
const addOrUpdateHandle = ({ id, name }: { id?: string; name?: string } = {}) => {addOrUpdateRef.value.init(id, name);
};
</script>
<template><el-dialog v-model="visible" :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :close-on-press-escape="false"><el-form :model="dataForm" :rules="rules" ref="dataFormRef" @keyup.enter="dataFormSubmitHandle()"><el-form-item prop="name" label="名称"><el-input v-model="dataForm.name" placeholder="名称"></el-input></el-form-item></el-form><template v-slot:footer><el-button @click="visible = false">取消</el-button><el-button type="primary" @click="dataFormSubmitHandle()">确定</el-button></template></el-dialog>
</template><script lang="ts" setup>
import { reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
const emit = defineEmits(['refreshDataList']);
const visible = ref(false);
const dataFormRef = ref();
// 表单数据
const dataForm = reactive({id: '',name: '',
});
// 表单规则
const rules = ref({name: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
});
// 初始化
const init = (id?: string, name?: string) => {visible.value = true;dataForm.id = '';dataForm.name = '';// 重置表单数据if (dataFormRef.value) {dataFormRef.value.resetFields();}if (id && name) {getInfo(id, name);}
};// 获取信息,因为信息很少,并且表格行中有数据,所以没有请求接口
const getInfo = (id: string, name: string) => {Object.assign(dataForm, {id,name,});
};// 表单提交
const dataFormSubmitHandle = () => {dataFormRef.value.validate((valid: boolean) => {if (!valid) {return false;}// 模拟接口请求Promise.resolve(true).then((res) => {ElMessage.success({message: '成功',duration: 500,onClose: () => {visible.value = false;emit('refreshDataList');},});});});
};defineExpose({init,
});
</script><style lang="less"></style>

多操作几次可以发现,如果第一次点击的是新增,那么不会出现问题;如果第一次点击的是修改,那么点击新增时,名称始终是第一次点击修改那行的名称。

分析

遇到不明白原因的问题,可以采取很多种方法,之前的博文分析中有使用过,但是没仔细说过。vue3自定义hooks遇到的问题中使用的是打印关键数据分析;安装react报错中使用了查看报错信息分析原因;antd的表格组件错乱问题中使用了查找仓库Issues和根据问题现象审查元素分析问题;还有之前没用过的注释排除法,把感觉有问题的代码区域(不好确定就尽量多)注释掉,一点一点缩小范围来排查出问题代码;当然还有使用更频繁的方法百度,现在AI这么流行的情况下,使用AI搜索更方便。

本次采用打印关键数据分析

<template><el-dialog v-model="visible" :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :close-on-press-escape="false"><el-form :model="dataForm" :rules="rules" ref="dataFormRef" @keyup.enter="dataFormSubmitHandle()"><el-form-item prop="name" label="名称"><el-input v-model="dataForm.name" placeholder="名称"></el-input></el-form-item></el-form><template v-slot:footer><el-button @click="visible = false">取消</el-button><el-button type="primary" @click="dataFormSubmitHandle()">确定</el-button></template></el-dialog>
</template><script lang="ts" setup>
import { reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
const emit = defineEmits(['refreshDataList']);
const visible = ref(false);
const dataFormRef = ref();
// 表单数据
const dataForm = reactive({id: '',name: '',
});
// 表单规则
const rules = ref({name: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
});
// 初始化
const init = (id?: string, name?: string) => {visible.value = true;dataForm.id = '';dataForm.name = '';console.log(dataForm);// 重置表单数据if (dataFormRef.value) {dataFormRef.value.resetFields();}console.log(dataForm);if (id && name) {getInfo(id, name);}console.log(dataForm);
};// 获取信息,因为信息很少,并且表格行中有数据,所以没有请求接口
const getInfo = (id: string, name: string) => {Object.assign(dataForm, {id,name,});
};// 表单提交
const dataFormSubmitHandle = () => {dataFormRef.value.validate((valid: boolean) => {if (!valid) {return false;}// 模拟接口请求Promise.resolve(true).then((res) => {ElMessage.success({message: '成功',duration: 500,onClose: () => {visible.value = false;emit('refreshDataList');},});});});
};defineExpose({init,
});
</script><style lang="less"></style>
修改点击1修改点击2修改点击3
打印1{“id”: “”, “name”: “”}{“id”: “”, “name”: “”}{“id”: “”, “name”: “”}
打印2{“id”: “”, “name”: “”}{“id”: “”, “name”: “name0”}{“id”: “”, “name”: “name0”}
打印3{“id”: 1, “name”: “name0”}{“id”: 7, “name”: “name6”}{“id”: 4, “name”: “name3”}

不要展开查看[[target]],因为这样都是一样的

从打印内容可以对比分析出问题是由代码dataFormRef.value.resetFields()导致的,那么直接注释掉这里,再次尝试看看,可以看到现在不会出现之前问题了,但是出现了新问题,表单校验状态没重置(添加代码dataFormRef.value.resetFields()也是为了解决表单状态重置的)。现在找到问题之后,我们尝试进行修改。

方法一

除了使用表单方法进行重置之外,还可以关闭弹窗进行销毁,在el-dialog上增加destroy-on-close,此方法可以解决问题,但是会产生额外DOM操作性能,不推荐

<template><el-dialog v-model="visible" :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :close-on-press-escape="false" destroy-on-close><el-form :model="dataForm" :rules="rules" ref="dataFormRef" @keyup.enter="dataFormSubmitHandle()"><el-form-item prop="name" label="名称"><el-input v-model="dataForm.name" placeholder="名称"></el-input></el-form-item></el-form><template v-slot:footer><el-button @click="visible = false">取消</el-button><el-button type="primary" @click="dataFormSubmitHandle()">确定</el-button></template></el-dialog>
</template><script lang="ts" setup>
import { reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
const emit = defineEmits(['refreshDataList']);
const visible = ref(false);
const dataFormRef = ref();
// 表单数据
const dataForm = reactive({id: '',name: '',
});
// 表单规则
const rules = ref({name: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
});
// 初始化
const init = (id?: string, name?: string) => {visible.value = true;dataForm.id = '';dataForm.name = '';// 重置表单数据// if (dataFormRef.value) {//   dataFormRef.value.resetFields();// }if (id && name) {getInfo(id, name);}
};// 获取信息,因为信息很少,并且表格行中有数据,所以没有请求接口
const getInfo = (id: string, name: string) => {Object.assign(dataForm, {id,name,});
};// 表单提交
const dataFormSubmitHandle = () => {dataFormRef.value.validate((valid: boolean) => {if (!valid) {return false;}// 模拟接口请求Promise.resolve(true).then((res) => {ElMessage.success({message: '成功',duration: 500,onClose: () => {visible.value = false;emit('refreshDataList');},});});});
};defineExpose({init,
});
</script><style lang="less"></style>
方法二

把代码dataFormRef.value.resetFields()位置提前,放到所有重置表单数据前面

<template><el-dialog v-model="visible" :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :close-on-press-escape="false"><el-form :model="dataForm" :rules="rules" ref="dataFormRef" @keyup.enter="dataFormSubmitHandle()"><el-form-item prop="name" label="名称"><el-input v-model="dataForm.name" placeholder="名称"></el-input></el-form-item></el-form><template v-slot:footer><el-button @click="visible = false">取消</el-button><el-button type="primary" @click="dataFormSubmitHandle()">确定</el-button></template></el-dialog>
</template><script lang="ts" setup>
import { reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
const emit = defineEmits(['refreshDataList']);
const visible = ref(false);
const dataFormRef = ref();
// 表单数据
const dataForm = reactive({id: '',name: '',
});
// 表单规则
const rules = ref({name: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
});
// 初始化
const init = (id?: string, name?: string) => {visible.value = true;// 重置表单数据if (dataFormRef.value) {dataFormRef.value.resetFields();}dataForm.id = '';dataForm.name = '';if (id && name) {getInfo(id, name);}
};// 获取信息,因为信息很少,并且表格行中有数据,所以没有请求接口
const getInfo = (id: string, name: string) => {Object.assign(dataForm, {id,name,});
};// 表单提交
const dataFormSubmitHandle = () => {dataFormRef.value.validate((valid: boolean) => {if (!valid) {return false;}// 模拟接口请求Promise.resolve(true).then((res) => {ElMessage.success({message: '成功',duration: 500,onClose: () => {visible.value = false;emit('refreshDataList');},});});});
};defineExpose({init,
});
</script><style lang="less"></style>
方法三

为什么一般人不悔遇到这个问题呢?因为通常情况下,修改的时候都是通过请求详情接口进行表单数据初始化的,所以这个方法就是为了延缓(异步)修改时数据赋值时间。

<template><el-dialog v-model="visible" :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :close-on-press-escape="false"><el-form :model="dataForm" :rules="rules" ref="dataFormRef" @keyup.enter="dataFormSubmitHandle()"><el-form-item prop="name" label="名称"><el-input v-model="dataForm.name" placeholder="名称"></el-input></el-form-item></el-form><template v-slot:footer><el-button @click="visible = false">取消</el-button><el-button type="primary" @click="dataFormSubmitHandle()">确定</el-button></template></el-dialog>
</template><script lang="ts" setup>
import { reactive, ref, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
const emit = defineEmits(['refreshDataList']);
const visible = ref(false);
const dataFormRef = ref();
// 表单数据
const dataForm = reactive({id: '',name: '',
});
// 表单规则
const rules = ref({name: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
});
// 初始化
const init = (id?: string, name?: string) => {visible.value = true;dataForm.id = '';dataForm.name = '';// 重置表单数据if (dataFormRef.value) {dataFormRef.value.resetFields();}if (id && name) {nextTick(() => getInfo(id, name));}
};// 获取信息,因为信息很少,并且表格行中有数据,所以没有请求接口
const getInfo = (id: string, name: string) => {Object.assign(dataForm, {id,name,});
};// 表单提交
const dataFormSubmitHandle = () => {dataFormRef.value.validate((valid: boolean) => {if (!valid) {return false;}// 模拟接口请求Promise.resolve(true).then((res) => {ElMessage.success({message: '成功',duration: 500,onClose: () => {visible.value = false;emit('refreshDataList');},});});});
};defineExpose({init,
});
</script><style lang="less"></style>

为什么修改时候延迟(异步)初始化可以呢?可以再打印看一下

<template><el-dialog v-model="visible" :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :close-on-press-escape="false"><el-form :model="dataForm" :rules="rules" ref="dataFormRef" @keyup.enter="dataFormSubmitHandle()"><el-form-item prop="name" label="名称"><el-input v-model="dataForm.name" placeholder="名称"></el-input></el-form-item></el-form><template v-slot:footer><el-button @click="visible = false">取消</el-button><el-button type="primary" @click="dataFormSubmitHandle()">确定</el-button></template></el-dialog>
</template><script lang="ts" setup>
import { reactive, ref, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
const emit = defineEmits(['refreshDataList']);
const visible = ref(false);
const dataFormRef = ref();
// 表单数据
const dataForm = reactive({id: '',name: '',
});
// 表单规则
const rules = ref({name: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
});
// 初始化
const init = (id?: string, name?: string) => {visible.value = true;dataForm.id = '';dataForm.name = '';console.log(dataForm);// 重置表单数据if (dataFormRef.value) {dataFormRef.value.resetFields();}console.log(dataForm);if (id && name) {nextTick(() => getInfo(id, name));}
};// 获取信息,因为信息很少,并且表格行中有数据,所以没有请求接口
const getInfo = (id: string, name: string) => {Object.assign(dataForm, {id,name,});console.log(dataForm);
};// 表单提交
const dataFormSubmitHandle = () => {dataFormRef.value.validate((valid: boolean) => {if (!valid) {return false;}// 模拟接口请求Promise.resolve(true).then((res) => {ElMessage.success({message: '成功',duration: 500,onClose: () => {visible.value = false;emit('refreshDataList');},});});});
};defineExpose({init,
});
</script><style lang="less"></style>
修改点击1修改点击2修改点击3
打印1{“id”: “”, “name”: “”}{“id”: “”, “name”: “”}{“id”: “”, “name”: “”}
打印2{“id”: “”, “name”: “”}{“id”: “”, “name”: “”}{“id”: “”, “name”: “”}
打印3{“id”: 1, “name”: “name0”}{“id”: 7, “name”: “name6”}{“id”: 4, “name”: “name3”}

可以看出延迟(异步)初始化之后,dataFormRef.value.resetFields()重置之后不会再把name赋值为第一次点击修改的name值,因此可以猜测到表单的初始值(resetFields()方法作用是把表单数据修改为初始值,并移除表单状态)是第一次初始化的同步代码的值(getInfo是同步修改的值时出现问题了),同步代码中相应字段(表单项prop指定的)没值,表单执行resetFields()方法时就会是没值。

项目地址

问题代码:https://gitee.com/lydxwj/vue-reactive/tree/error

方法一:https://gitee.com/lydxwj/vue-reactive/tree/method1/

方法二:https://gitee.com/lydxwj/vue-reactive/tree/method2/

方法三:https://gitee.com/lydxwj/vue-reactive/tree/method3/

分析原因:https://gitee.com/lydxwj/vue-reactive


http://www.ppmy.cn/server/176210.html

相关文章

React 和 Vue 框架概念及区别

React 和 Vue 是什么&#xff1f; React 和 Vue 都是 前端开发框架&#xff0c;用来构建网页或应用的用户界面&#xff08;UI&#xff09;。它们能帮助开发者更高效地管理网页的结构、交互和数据更新&#xff0c;使网页变得更智能、更流畅。 简单来说&#xff1a; React 是 …

最新!Ubuntu Docker 安装教程

源自: AINLPer&#xff08;每日干货分享&#xff01;&#xff01;&#xff09; 编辑: ShuYini 校稿: ShuYini 时间: 2025-3-1 更多&#xff1a;>>>>大模型/AIGC、学术前沿的知识分享&#xff01; 看到很多部署大模型的时候&#xff0c;都是基于docker安装部署的。…

【Node.js入门笔记8---fs的一些高级功能】

Node.js入门笔记8 Node.js---fs高级功能一、fs高级功能1. fs.chmod()&#xff1a;修改文件权限&#xff08;如 0o755&#xff09;。2. fs.chown()&#xff1a;修改文件所有者。3. 使用 fs.promises 下的方法。4. 用 util.promisify() 转换回调风格的函数。 Node.js—fs高级功能…

K8s的部署

准备三台虚拟机 作用IP地址操作系统配置关键组件k8s-master01172.16.90.11Rocky Linux release 92颗CPU 4G内存 50G硬盘kube-apiserver, etcd, etck8s-node01172.16.90.12Rocky Linux release 92颗CPU 4G内存 50G硬盘kubelet, kube-proxyk8s-node02172.16.90.13Rocky Linux re…

CSS Sprite (精灵图/雪碧图)

CSS Sprite 目录应用场景1.网页性能优化2.用户交互场景 代码实现1.目录结构2.练习图片地址3.代码 目录 应用场景 1.网页性能优化 减少 HTTP 请求&#xff1a;在传统的网页中&#xff0c;如果有多个小图标或小图片&#xff0c;浏览器需要为每个图片单独发起一个 HTTP 请求。这…

Tomcat新手入门指南:从零开始安装与基本配置

Tomcat新手入门指南&#xff1a;从零开始安装与基本配置 Tomcat 是一个广泛使用的Java servlet容器&#xff0c;用于运行Web应用服务器。它将JavaBean转换为Servlet&#xff0c;并作为JVM的一个虚拟进程运行。以下是一步一步的教程&#xff0c;帮助您快速安装和配置Tomcat。 …

【Spring】第四弹:基于XML文件注入Bean对象

一、setter 注入Bean对象 1.创建Student对象 public class Student {private Integer id;private String name;private Integer age;private String sex;public Student() {}public Integer getId() {return id;}public void setId(Integer id) {this.id id;}public String …

Linux主机持久化技术

1.SSH软连接后门 SSH服务允许通过PAM进行认证&#xff0c;关键文件&#xff1a;sshd_config;确保UserPAM的值为YES 我们可以看一下/etc/ssh/sshd_config的配置&#xff0c;ssh是客户端配置&#xff0c;sshd是服务端配置 如上可以知道这个是允许PAM认证的 在linux中存在模块pam…