reactive数据修改无效
环境
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