环境
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