在异步与请求的频繁使用情况下,导致的渲染与数据获取不同步的问题及其解决方法,以生产指令表单为例
在开发复杂的前端应用时,我们经常会遇到异步操作和频繁的请求。这些操作可能会导致渲染与数据获取不同步的问题,从而影响用户体验甚至导致功能异常。本文将以一个生产指令表单为例,探讨在异步与请求频繁使用情况下,如何解决渲染与数据获取不同步的问题。
一、问题背景
生产指令表单是一个复杂的表单页面,涉及多个模块的数据交互,包括基础信息、生产产品选择、物料清单等。在实际开发中,我们需要从后端获取大量的数据,并在页面上进行动态渲染。由于数据量较大且涉及多个异步请求,很容易出现数据获取与页面渲染不同步的问题。
例如,在选择生产产品时,我们需要根据产品的编号获取其详细信息,包括 BOM(物料清单)、工艺路线和设计图纸等。这些数据需要通过异步请求获取,并在表格中动态渲染。如果数据获取与渲染不同步,可能会导致以下问题:
页面显示空白或错误数据。
数据更新后,页面未能及时刷新。
用户操作(如添加、删除、修改)后,数据与页面状态不一致。
二、问题分析
1. 异步数据获取与页面渲染的冲突
在生产指令表单中,我们使用了 Vue 的 ref
和 reactive
来管理数据,并通过异步请求获取数据。例如,在 handleInnerCode
方法中,我们根据产品编号获取其详细信息:
JavaScript复制
async function handleInnerCode(goodsCode, index) {
try {
const result = await productInformationApi.getProductInformationByGoodsCode(goodsCode);
return handleInnerInformation(result.data.goodsBomCode, result.data.processRouteCode, index);
} catch (error) {
console.error('产品详情 API 调用失败:', error);
}
}
然而,由于异步请求的延迟,数据可能在页面渲染时还未完全获取到,导致页面显示空白或错误数据。
2. 数据更新与页面刷新的延迟
在表格中,我们使用了动态数据绑定来渲染产品信息。例如,tableData
是一个 ref
,用于存储产品列表的数据:
JavaScript复制
const tableData = ref([]);
当数据更新时,我们需要将新数据赋值给 tableData
,以触发页面的重新渲染。然而,由于异步请求的延迟,数据更新可能会滞后于用户操作,导致页面刷新不及时。
3. 用户操作与数据状态的不一致
在用户操作(如添加、删除、修改)后,我们需要更新数据并刷新页面。例如,在删除产品时,我们调用了 requestDelete
方法:
JavaScript复制
async function requestDelete(data) {
SmartLoading.show();
try {
tableData.value = tableData.value.filter((item) => item.id !== data.id);
message.success('删除成功');
} catch (e) {
smartSentry.captureError(e);
} finally {
SmartLoading.hide();
}
}
然而,由于异步操作的延迟,删除操作可能会失败或数据未及时更新,导致用户操作与数据状态不一致。
三、解决方法
1. 使用 nextTick
确保 DOM 更新
在 Vue 中,我们可以使用 nextTick
来确保 DOM 已经更新。例如,在数据获取完成后,我们可以使用 nextTick
来刷新页面:
JavaScript复制
async function handleInnerCode(goodsCode, index) {
try {
const result = await productInformationApi.getProductInformationByGoodsCode(goodsCode);
const innerInformation = await handleInnerInformation(result.data.goodsBomCode, result.data.processRouteCode, index);
nextTick(() => {
tableData.value[index].productBom = innerInformation.bomList;
tableData.value[index].productProcessRoute = innerInformation.processList;
});
} catch (error) {
console.error('产品详情 API 调用失败:', error);
}
}
通过 nextTick
,我们可以确保数据更新后,页面能够及时刷新。
2. 使用加载状态控制渲染
在数据获取过程中,我们可以使用加载状态来控制页面的渲染。例如,在 handlePurchaseOrderChange
方法中,我们可以在数据获取完成后关闭加载状态:
JavaScript复制
async function handlePurchaseOrderChange(saleOrderCode) {
if (saleOrderCode) {
try {
const res = await saleOrderApi.getSaleOrderByCode(saleOrderCode);
tableLoading.value = true;
await getGoodsBomModuleList(res.data.goodsList.length);
form.value.customerCode = res.data.customerCode;
activeKeyInner.value = [];
bomSelectedRowKeyList.value = [];
processSelectedRowKeyList.value = [];
if (res.ok && res.data.goodsList) {
const goodsList = res.data.goodsList;
const promises = goodsList.map(async (item, index) => {
item.expectedReceiptGoodsNumber = item.goodsNumber;
item.actuallyReceiptGoodsNumber = item.goodsNumber;
item.designDrawing = [];
activeKeyInner.value.push(['1', '2', '3']);
bomSelectedRowKeyList.value.push([]);
processSelectedRowKeyList.value.push([]);
const innerInformation = await handleInnerCode(item.goodsCode, index);
item.productBom = innerInformation.bomList;
item.productProcessRoute = innerInformation.processList;
return item;
});
const updatedGoodsList = await Promise.all(promises);
tableData.value = updatedGoodsList;
total.value = tableData.value.length;
updateMaterialData();
} else {
console.error('获取商品数据失败:', res.msg);
}
} catch (error) {
console.error('产品列表 API 调用失败:', error);
} finally {
tableLoading.value = false;
}
} else {
tableData.value = [];
total.value = 0;
}
}
通过加载状态,我们可以避免在数据未完全获取时渲染页面。
3. 使用防抖和节流优化操作
在用户操作频繁的情况下,我们可以使用防抖和节流来优化操作。例如,在 updateMaterialData
方法中,我们可以使用防抖来避免频繁的更新:
JavaScript复制
const debounceUpdateMaterialData = _.debounce(async () => {
const materialDataMap = tableDataMap.value.reduce((acc, row, rowIndex) => {
Object.values(row).forEach((itemArray) => {
itemArray.forEach((item) => {
const { goodsCode, materialsRequired } = item;
const parentProduct = tableData.value[rowIndex];
const productQuantity = parentProduct.goodsNumber || 1;
if (acc[goodsCode]) {
acc[goodsCode].materialsRequired += materialsRequired * productQuantity;
} else {
acc[goodsCode] = { ...item, materialsRequired: materialsRequired * productQuantity };
}
});
});
return acc;
}, {});
materialData.value = Object.values(materialDataMap);
for (let i = 0; i < materialData.value.length; i++) {
const item = materialData.value[i];
const res = await inventoryApi.getInventoryGoodsNumber(item);
materialData.value[i].goodsAvailableNumber = res.data.goodsAvailableNumber;
if (materialData.value[i].materialsRequired > materialData.value[i].goodsAvailableNumber) {
materialData.value[i].goodsPurchasedNumber = materialData.value[i].materialsRequired - materialData.value[i].goodsAvailableNumber;
} else {
materialData.value[i].goodsPurchasedNumber = 0;
}
}
}, 300);
通过防抖,我们可以避免在用户频繁操作时触发过多的更新。
4. 使用状态管理确保数据一致性
在复杂的表单中,我们可以使用状态管理来确保数据一致性。例如,在 form
中,我们可以使用 reactive
来管理表单数据,并在数据更新时确保状态一致:
JavaScript复制
const form = reactive({
produceInstructOrderCode: undefined,
produceInstructOrderName: undefined,
priority: undefined,
manager: undefined,
managerName: undefined,
maker: undefined,
makerName: undefined,
saleOrderCode: undefined,
customerCode: undefined,
customerName: undefined,
plannedStartTime: undefined,
plannedEndTime: undefined,
remark: undefined,
productList: [],
});
在数据更新时,我们可以使用 Object.assign
来确保状态一致:
JavaScript复制
if (route.query.produceInstructOrderCode) {
const res = await produceInstructOrderApi.getProduceInstructOrderByCode(route.query.produceInstructOrderCode);
Object.assign(form, res.data);
}
通过状态管理,我们可以确保数据更新时,页面状态保持一致。
四、总结
在异步与请求频繁使用的情况下,渲染与数据获取不同步的问题是常见的。通过使用 nextTick
确保 DOM 更新、使用加载状态控制渲染、使用防抖和节流优化操作以及使用状态管理确保数据一致性,我们可以有效解决这些问题。在实际开发中,我们需要根据具体场景选择合适的解决方案,以确保页面的稳定性和用户体验。