React 组件在防止重复的情况下执行两次同步操作

问题描述 投票:0回答:1

我遇到了一个具有挑战性的问题,即我的 React 组件(旨在与后端同步数据)快速连续执行两次同步操作。尽管我已经采取了多种措施来防止此类重复,但这种情况还是会发生。

背景:

框架/库版本:使用 Dexie 来响应 IndexedDB,使用 axios 来响应 HTTP 请求。 组件目标:SyncWorker 组件旨在在检测到在线状态时将未同步的项目从 IndexedDB 同步到后端服务器。 重复操作问题:尽管采取了防止重复操作的措施,但同步操作还是执行了两次,大约相隔一秒,由后端日志和客户端观察确认。

详细组件实现:

import { useCallback, useEffect, useRef, useState } from "react";
import { useLiveQuery } from "dexie-react-hooks";
import axios from "axios";
import { DeviceConfiguration, db } from "../../db"; // IndexedDB setup
import uploadFile from "../helpers/uploadFile"; // Handles file uploads
import useFirebase from "../hooks/useFirebase"; // Custom hook for Firebase storage
import { base64ToFile } from "../helpers/base64ToFile"; // Utility for file conversions

function SyncWorker() {
  const queryResults = useLiveQuery(() => db.configurations.toArray(), []);
  const { storage } = useFirebase();
  const [isOnline, setIsOnline] = useState(navigator.onLine);
  const processingIdsRef = useRef<Set<number>>(new Set());

  const syncItems = useCallback(async (unsynced: DeviceConfiguration[]) => {
    for (const item of unsynced) {
      if (processingIdsRef.current.has(item.id!)) continue;
      processingIdsRef.current.add(item.id!);
      console.log("Processing id: ", item.id);

      const imageAsFile = base64ToFile(item.photo, `${item.position_identifier}_${item.serial_number}_${new Date().toISOString()}`);

      try {
        const imageUrl = await uploadFile(storage!, imageAsFile, `jobs/${item.ongoing_job_id}/${item.position_identifier}`, imageAsFile.name);
        
        await axios.patch(`${process.env.REACT_APP_BACKEND_URL}/ongoing-jobs/configure-device`, {
          ongoing_job_id: item.ongoing_job_id,
          type: item.type,
          image_url: imageUrl,
          position_identifier: item.position_identifier,
          serial_number: item.serial_number,
        });

        await db.configurations.update(item.id!, { synced: true });
        processingIdsRef.current.delete(item.id!);
      } catch (err) {
        processingIdsRef.current.delete(item.id!);
        console.error("Error syncing item: ", err);
      }
    }
  }, [storage]);

  useEffect(() => {
    if (!queryResults || !isOnline) return;
    const unsynced = queryResults.filter(item => !item.synced);
    if (unsynced.length === 0) return;
    console.log("Starting sync for", unsynced.length, "items");
    syncItems(unsynced);
  }, [queryResults, isOnline, syncItems]);

  return null;
}

export default SyncWorker;

观察和调试尝试:

  • 服务器端日志:将日志放置在服务器上的路由处理程序的开头时,它会为来自客户端的单个同步操作启动打印两次,表明服务器收到两个请求。
  • 客户端日志:我的 React 组件中的日志 (console.log("Startingsync for", unsynced.length, "items")) 在浏览器控制台中打印一次。但是,使用 Console Ninja(一种在 VS Code 中显示内联日志的工具),相同的日志会出现两次,尽管间隔很近(大约相隔一秒)。
  • 采取的措施:我已经确认禁用了 React Strict 模式,以排除其出于调试目的而加倍生命周期方法的影响。此外,我使用了 Set 来按 ID 跟踪处理项目,旨在防止重新处理已同步的项目。

问题:

  • 什么可能导致同步操作的重复执行,特别是考虑到预防措施到位?
  • 考虑到不同日志记录方法之间的差异,如何进一步诊断或调试此问题?
  • useEffect 或 useCallback 挂钩的任何方面,或者管理和更新状态的方式是否可能无意中触发重复操作?
javascript reactjs indexeddb
1个回答
0
投票

这其实很正常,如果你激活了react严格模式(你应该),开发模式下的所有useEffects都会执行两次。

在官方 React 文档中,建议将您的应用程序部署到临时环境(在生产模式下运行)或暂时选择退出严格模式及其仅用于开发的重新安装检查。

如果这个解决方案不是您想要的,您可以使用参考来检查效果是否已经执行。

 const isMountedRef = useRef(false);

  useEffect(() => {
    if (!isMountedRef.current) {
      console.log('useEffect executes for the first time.');
      
      // Change reference value.
      isMountedRef.current = true;
    }
  }

© www.soinside.com 2019 - 2024. All rights reserved.