fix: 修复App.tsx中useEffect依赖死循环导致题目不断自动刷新的问题
This commit is contained in:
+38
-21
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import questionsData from './questions.json';
|
import questionsData from './questions.json';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -23,11 +23,19 @@ export default function App() {
|
|||||||
const [totalAnswered, setTotalAnswered] = useState(0);
|
const [totalAnswered, setTotalAnswered] = useState(0);
|
||||||
const [isSyncing, setIsSyncing] = useState(false);
|
const [isSyncing, setIsSyncing] = useState(false);
|
||||||
|
|
||||||
|
// 使用 ref 避免因 weights 变化触发 pickRandomQuestion 重新创建导致死循环
|
||||||
|
const weightsRef = useRef<Record<number, number>>({});
|
||||||
|
|
||||||
|
const updateWeights = useCallback((newWeights: Record<number, number>) => {
|
||||||
|
weightsRef.current = newWeights;
|
||||||
|
setWeights(newWeights);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 随机挑选一道题目(基于权重轮盘赌算法)
|
// 随机挑选一道题目(基于权重轮盘赌算法)
|
||||||
const pickRandomQuestion = useCallback((latestWeights?: Record<number, number>) => {
|
const pickRandomQuestion = useCallback((latestWeights?: Record<number, number>) => {
|
||||||
if (questions.length === 0) return;
|
if (questions.length === 0) return;
|
||||||
|
|
||||||
const currentWeights = latestWeights || weights;
|
const currentWeights = latestWeights || weightsRef.current;
|
||||||
let totalWeight = 0;
|
let totalWeight = 0;
|
||||||
questions.forEach(q => {
|
questions.forEach(q => {
|
||||||
totalWeight += (currentWeights[q.id] || 1);
|
totalWeight += (currentWeights[q.id] || 1);
|
||||||
@@ -47,38 +55,47 @@ export default function App() {
|
|||||||
setCurrentQuestion(selectedQ);
|
setCurrentQuestion(selectedQ);
|
||||||
setSelectedOptions([]);
|
setSelectedOptions([]);
|
||||||
setIsSubmitted(false);
|
setIsSubmitted(false);
|
||||||
}, [questions, weights]);
|
}, [questions]);
|
||||||
|
|
||||||
// 从服务端拉取全局统计与权重数据
|
// 从服务端拉取全局统计与权重数据并初始化
|
||||||
const fetchGlobalStats = useCallback(async () => {
|
useEffect(() => {
|
||||||
|
let isMounted = true;
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
let loadedWeights: Record<number, number> = {};
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/stats');
|
const res = await fetch('/api/stats');
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.weights) setWeights(data.weights);
|
if (data.weights) loadedWeights = data.weights;
|
||||||
|
if (isMounted) {
|
||||||
if (data.history) setHistory(data.history);
|
if (data.history) setHistory(data.history);
|
||||||
if (typeof data.totalAnswered === 'number') setTotalAnswered(data.totalAnswered);
|
if (typeof data.totalAnswered === 'number') setTotalAnswered(data.totalAnswered);
|
||||||
return data.weights;
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('获取全局统计数据失败,使用本地兜底:', e);
|
console.warn('获取全局统计数据失败,使用本地兜底:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 兜底本地逻辑
|
if (Object.keys(loadedWeights).length === 0) {
|
||||||
const initialWeights: Record<number, number> = {};
|
|
||||||
questions.forEach(q => {
|
questions.forEach(q => {
|
||||||
initialWeights[q.id] = 1;
|
loadedWeights[q.id] = 1;
|
||||||
});
|
});
|
||||||
setWeights(initialWeights);
|
}
|
||||||
return initialWeights;
|
|
||||||
}, [questions]);
|
|
||||||
|
|
||||||
// 初始化加载
|
if (isMounted) {
|
||||||
useEffect(() => {
|
updateWeights(loadedWeights);
|
||||||
fetchGlobalStats().then((loadedWeights) => {
|
|
||||||
pickRandomQuestion(loadedWeights);
|
pickRandomQuestion(loadedWeights);
|
||||||
});
|
}
|
||||||
}, [fetchGlobalStats, pickRandomQuestion]);
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
};
|
||||||
|
}, [questions, updateWeights, pickRandomQuestion]);
|
||||||
|
|
||||||
|
|
||||||
const handleOptionClick = (optionLabel: string) => {
|
const handleOptionClick = (optionLabel: string) => {
|
||||||
if (isSubmitted) return;
|
if (isSubmitted) return;
|
||||||
@@ -111,10 +128,10 @@ export default function App() {
|
|||||||
setHistory(localNewHistory);
|
setHistory(localNewHistory);
|
||||||
setTotalAnswered(prev => prev + 1);
|
setTotalAnswered(prev => prev + 1);
|
||||||
|
|
||||||
const localWeights = { ...weights };
|
const localWeights = { ...weightsRef.current };
|
||||||
const currentWeight = localWeights[currentQuestion.id] || 1;
|
const currentWeight = localWeights[currentQuestion.id] || 1;
|
||||||
localWeights[currentQuestion.id] = isCorrect ? Math.max(1, currentWeight - 1) : currentWeight + 3;
|
localWeights[currentQuestion.id] = isCorrect ? Math.max(1, currentWeight - 1) : currentWeight + 3;
|
||||||
setWeights(localWeights);
|
updateWeights(localWeights);
|
||||||
|
|
||||||
// 异步同步到后端服务端
|
// 异步同步到后端服务端
|
||||||
setIsSyncing(true);
|
setIsSyncing(true);
|
||||||
@@ -132,7 +149,7 @@ export default function App() {
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.weights) {
|
if (data.weights) {
|
||||||
setWeights(data.weights);
|
updateWeights(data.weights);
|
||||||
nextWeights = data.weights;
|
nextWeights = data.weights;
|
||||||
}
|
}
|
||||||
if (data.history) setHistory(data.history);
|
if (data.history) setHistory(data.history);
|
||||||
|
|||||||
Reference in New Issue
Block a user