以下是基于Vue3的代码示例,实现父组件表格行点击通过对话框子组件编辑数据的功能:
vue
<!-- 父组件 ParentComponent.vue --> <template> <el-table :data="tableData" @row-click="handleRowClick" style="width: 100%"> <el-table-column prop="name" label="Name" /> <el-table-column prop="age" label="Age" /> <el-table-column prop="email" label="Email" /> </el-table> <ChildDialog v-model="dialogVisible" :row-data="selectedRow" @confirm="handleDataUpdate" /> </template> <script setup> import { ref } from 'vue' import ChildDialog from './ChildDialog.vue' const tableData = ref([ { id: 1, name: 'John', age: 30, email: 'john@example.com' }, { id: 2, name: 'Jane', age: 25, email: 'jane@example.com' } ]) const dialogVisible = ref(false) const selectedRow = ref(null) const handleRowClick = (row) => { selectedRow.value = row dialogVisible.value = true } const handleDataUpdate = (updatedData) => { const index = tableData.value.findIndex(item => item.id === updatedData.id) if (index !== -1) { tableData.value.splice(index, 1, updatedData) } } </script>
vue
<!-- 子组件 ChildDialog.vue --> <template> <el-dialog v-model="modelValue" title="Edit Row" width="500px" @close="handleClose" > <el-form label-width="80px"> <el-form-item label="Name"> <el-input v-model="localData.name" /> </el-form-item> <el-form-item label="Age"> <el-input v-model.number="localData.age" /> </el-form-item> <el-form-item label="Email"> <el-input v-model="localData.email" /> </el-form-item> </el-form> <template #footer> <el-button @click="modelValue = false">Cancel</el-button> <el-button type="primary" @click="handleConfirm">Confirm</el-button> </template> </el-dialog> </template> <script setup> import { ref, watch, defineProps, defineEmits } from 'vue' const props = defineProps({ modelValue: Boolean, rowData: Object }) const emit = defineEmits(['update:modelValue', 'confirm']) const localData = ref({...props.rowData}) watch(() => props.rowData, (newVal) => { localData.value = {...newVal} }) watch(() => props.modelValue, (newVal) => { if (!newVal) return localData.value = {...props.rowData} }) const handleConfirm = () => { emit('confirm', {...localData.value}) emit('update:modelValue', false) } const handleClose = () => { emit('update:modelValue', false) } </script>
关键实现说明:
父子组件通信:
使用v-model双向绑定对话框状态
通过props传递行数据
通过emit事件进行数据更新通知
数据管理:
子组件使用localData维护数据副本
使用watch同步父组件传入的最新数据
在对话框打开时重置为原始数据
数据更新流程:
点击表格行时缓存选中数据并打开对话框
子组件深度拷贝数据保证独立性
修改数据后通过confirm事件回传父组件
父组件更新原始数据源完成响应式更新
注意事项:
使用{...object}进行对象浅拷贝
数字类型字段使用v-model.number修饰符
通过splice方法触发数组响应式更新
对话框关闭时重置本地数据
该实现方案完整实现了需求中的三个核心功能点,并保证了数据修改的独立性和响应式更新。
代码中关于两个watch的说明:
在 Vue3 的代码示例中,这两个 watch
各有不同的职责,后者并非多余。它们的核心区别如下:
1️⃣ 第一个 watch 的作用:props.rowData
的变化监听
javascript
watch(() => props.rowData, (newVal) => { localData.value = {...newVal} })
触发条件:当父组件传递的
rowData
引用发生变化时(例如切换不同行)。典型场景:用户点击不同表格行时,父组件传入新的
rowData
,子组件需同步更新本地数据。必要性:确保子组件能响应父组件数据源的变更。
2️⃣ 第二个 watch 的作用:props.modelValue
(对话框状态)的变化监听
javascript
watch(() => props.modelValue, (newVal) => { if (!newVal) return localData.value = {...props.rowData} })
触发条件:当对话框重新打开时(无论是否切换行)。
典型场景:用户关闭对话框后,再次打开同一行进行编辑。
必要性:解决对象引用未变时的数据同步问题。如果用户未保存修改就关闭对话框,再次打开时需要丢弃未保存的改动,还原到原始数据。
🚨 为什么需要两个 watch?
场景 | 第一个 watch 是否生效 | 第二个 watch 是否生效 | 问题描述 |
---|---|---|---|
点击不同行打开对话框 | ✅ 生效 | ❌ 不生效 | 需通过第一个 watch 同步新行数据 |
同一行重复打开对话框 | ❌ 不生效(引用未变) | ✅ 生效 | 需通过第二个 watch 重置本地数据,丢弃未保存的修改 |
父组件外部更新数据后打开对话框 | ✅ 生效 | ✅ 生效 | 双重保障确保数据同步 |
🔧 代码优化建议
如果希望进一步简化逻辑,可合并为一个 watch
,但需明确处理两种场景:
javascript
watch( [() => props.rowData, () => props.modelValue], ([newRowData, isOpen]) => { if (isOpen) { localData.value = {...newRowData} } }, { immediate: true } )
原理:当对话框打开时(
modelValue
为true
),强制用最新的rowData
重置本地数据。代价:可能牺牲部分性能(频繁触发),但逻辑更集中。
📝 总结
保留两个 watch:更符合职责单一原则,分别处理数据引用变化和对话框状态变化。
关键点:确保用户在重复打开同一行时,总能基于父组件的最新数据重新开始编辑,避免脏数据残留。