我遇到了一个具有挑战性的问题,即我的 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严格模式(你应该),开发模式下的所有useEffects都会执行两次。
在官方 React 文档中,建议将您的应用程序部署到临时环境(在生产模式下运行)或暂时选择退出严格模式及其仅用于开发的重新安装检查。
如果这个解决方案不是您想要的,您可以使用参考来检查效果是否已经执行。
const isMountedRef = useRef(false);
useEffect(() => {
if (!isMountedRef.current) {
console.log('useEffect executes for the first time.');
// Change reference value.
isMountedRef.current = true;
}
}