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