在异步与请求的频繁使用情况下,导致的渲染与数据获取不同步的问题及其解决方法,以生产指令表单为例

在开发复杂的前端应用时,我们经常会遇到异步操作和频繁的请求。这些操作可能会导致渲染与数据获取不同步的问题,从而影响用户体验甚至导致功能异常。本文将以一个生产指令表单为例,探讨在异步与请求频繁使用情况下,如何解决渲染与数据获取不同步的问题。

一、问题背景

生产指令表单是一个复杂的表单页面,涉及多个模块的数据交互,包括基础信息、生产产品选择、物料清单等。在实际开发中,我们需要从后端获取大量的数据,并在页面上进行动态渲染。由于数据量较大且涉及多个异步请求,很容易出现数据获取与页面渲染不同步的问题。

例如,在选择生产产品时,我们需要根据产品的编号获取其详细信息,包括 BOM(物料清单)、工艺路线和设计图纸等。这些数据需要通过异步请求获取,并在表格中动态渲染。如果数据获取与渲染不同步,可能会导致以下问题:

  • 页面显示空白或错误数据。

  • 数据更新后,页面未能及时刷新。

  • 用户操作(如添加、删除、修改)后,数据与页面状态不一致。

二、问题分析

1. 异步数据获取与页面渲染的冲突

在生产指令表单中,我们使用了 Vue 的 refreactive 来管理数据,并通过异步请求获取数据。例如,在 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 更新、使用加载状态控制渲染、使用防抖和节流优化操作以及使用状态管理确保数据一致性,我们可以有效解决这些问题。在实际开发中,我们需要根据具体场景选择合适的解决方案,以确保页面的稳定性和用户体验。


在异步与请求的频繁使用情况下,导致的渲染与数据获取不同步的问题及其解决方法,以生产指令表单为例
http://localhost:8090//archives/yibu-666
作者
冯斌杰
发布于
2025年03月02日
许可协议