Vue3组合式API+DevExtreme:关闭模态框后表格行不更新
Vue3 + DevExtreme 组合式API下表格数据源更新后不刷新问题
将应用从Vue2迁移到Vue3,使用组合式API和DevExtreme框架时遇到响应式异常:表格数据源为对象数组,双击行打开模态框编辑数据,点击保存后数据源数组已更新,但表格行未像Vue2中那样自动刷新。
相关代码
v-reqtype-manager.vue
<template> <div> <v-data-table :data-source="reqtypes" key-expr="fbReqtypeid" :columns="columns" @row-dbl-click="rowDblClick" > ... </v-data-table> <v-app-types-modal ref="appTypesModal" @ok="onOk" /> </div> </template> <script> ... export default { setup() { const appTypesModal = ref(null) let reqtypes = reactive([ { fbReqtypeid: "1", listItem: 'aaaa', type: "AAAA", link: "https://yandex.ru", digits: '1234', typeName: "Type A", genericName: "Generic A", singleLineText: "https://yandex.ru", multiLineText: "Japan is a sovereign island nation in East Asia. Located in the Pacific Ocean, it lies off the eastern coast of the Asian mainland and stretches from the Sea of Okhotsk in the north to the East China Sea and China in the southwest.", }, { fbReqtypeid: "2", listItem: 'bbbb', type: "BBBB", link: "https://stackoverflow.com", digits: '5678', typeName: "Type B", genericName: "Generic B", singleLineText: "Japan is a sovereign island nation in East Asia.", multiLineText: "Japan is a sovereign island nation in East Asia. Located in the Pacific Ocean, it lies off the eastern coast of the Asian mainland and stretches from the Sea of Okhotsk in the north to the East China Sea and China in the southwest. The Greater Tokyo Area is the most populous metropolitan area in the world.", }, ]) const columns = reactive([ { dataField: "fbReqtypeid", caption: "ID", dataType: "string", visible: false }, { dataField: "listItem", caption: "List item", dataType: "string" }, { dataField: "type", caption: "Request type", dataType: "string", cellTemplate: "link-to-modal" }, { dataField: "link", caption: "Link", dataType: "string" }, { dataField: "digits", caption: "Digits", dataType: "string" }, { dataField: "typeName", caption: "Request type name", dataType: "string" }, { dataField: "genericName", caption: "Generic name", dataType: "string" }, { dataField: "singleLineText", caption: "Single line", dataType: "string" }, { dataField: "multiLineText", caption: "Multi line", dataType: "string" }, ]) function rowDblClick(e) { showModal(e.data); } function showModal(selectedReqtype) { appTypesModal.value.showModal(selectedReqtype); } function onOk(obj) { const indexToUpdate = reqtypes.findIndex(item => item.fbReqtypeid === obj.fbReqtypeid); reqtypes.splice(indexToUpdate, 1, obj); } return { reqtypes, columns, rowDblClick, showModal, onOk, appTypesModal } } } </script>
v-data-table.vue
<template> <DxDataGrid v-bind="$attrs" :auto-navigate-to-focused-row="true" :focused-row-enabled="true" :hover-state-enabled="true" :show-borders="false" :show-column-lines="true" > <template v-for="(_, slot) in $slots" #[slot]="props"> <slot :name="slot" v-bind="props || {}" /> </template> <slot name="tools" /> <DxLoadPanel :enabled="true" /> <DxFilterRow :visible="isFilterRowEnabled" /> <DxSelection mode="single" /> <DxScrolling v-if="isVirtualScrollingEnabled" mode="virtual" /> <DxPaging v-else :page-size="pageSize" :enabled="isPageSizeEnabled" /> <DxPager /> </DxDataGrid> </template> <script> import { DxDataGrid, DxScrolling, DxSelection, DxPaging, DxFilterRow, DxPager, DxLoadPanel, } from "devextreme-vue/data-grid"; export default { inheritAttrs: false, components: { DxDataGrid, DxScrolling, DxSelection, DxFilterRow, DxPaging, DxPager, DxLoadPanel, }, props: { pageSize: { type: Number, default: 2, }, isPageSizeEnabled: { type: Boolean, default: false, }, isFilterRowEnabled: { type: Boolean, default: false, }, isVirtualScrollingEnabled: { type: Boolean, default: true, }, }, setup() { return { } } }; </script> <style scoped> .add-button { border: 0px; } :deep(.dx-button-content) { padding: inherit; } :deep(.dx-icon) { border: 1px solid #8ca8cc; border-radius: 16px; padding: 4px; } :deep(.dx-datagrid-text-content) { color: #0d356c; font-size: 14px; font-weight: 700; font-family: HelveticaNeueCyr, Helvetica, Arial, sans-serif; } .loading-indicator { text-align: center; padding-top: 110px; } </style>
问题原因与解决办法
原因分析
DevExtreme的DxDataGrid对Vue3响应式数组的变更感知逻辑和Vue2不同:使用splice替换数组内部元素时,Vue3的reactive能追踪到数组变化,但DevExtreme组件无法识别这种局部变更,导致表格未触发刷新;此外模态框返回的obj可能是非响应式对象,进一步加剧了数据同步问题。
解决办法
方案1:通过替换整个数组触发响应式更新
将reqtypes改为ref包裹,更新时生成新数组替换原数组,确保DevExtreme感知到数据源变更:
// 修改reqtypes的定义 const reqtypes = ref([ // ...原数组内容 ]) // 修改onOk函数 function onOk(obj) { reqtypes.value = reqtypes.value.map(item => item.fbReqtypeid === obj.fbReqtypeid ? obj : item ) }
方案2:调用DevExtreme API强制刷新表格
- 在
v-data-table.vue中暴露表格实例和刷新方法:
// v-data-table.vue的setup中 import { ref } from 'vue'; export default { // ...其他代码 setup() { const dataGridRef = ref(null); const refreshTable = () => { dataGridRef.value?.instance.refresh(); }; return { dataGridRef, refreshTable } } } // 模板中给DxDataGrid添加ref <DxDataGrid ref="dataGridRef" // ...原有属性 >
- 在
v-reqtype-manager.vue中调用刷新方法:
// v-reqtype-manager.vue的setup中 const dataTable = ref(null); // 修改onOk函数 function onOk(obj) { const indexToUpdate = reqtypes.findIndex(item => item.fbReqtypeid === obj.fbReqtypeid); reqtypes.splice(indexToUpdate, 1, obj); dataTable.value.refreshTable(); } // 模板中给v-data-table添加ref <v-data-table ref="dataTable" // ...原有属性 >
方案3:确保更新对象为响应式
如果模态框返回的obj是非响应式对象,用reactive包裹后再替换数组元素:
import { reactive } from 'vue'; function onOk(obj) { const indexToUpdate = reqtypes.findIndex(item => item.fbReqtypeid === obj.fbReqtypeid); reqtypes[indexToUpdate] = reactive(obj); }
内容的提问来源于stack exchange,提问作者tesicg
相关产品推荐
相关产品推荐

