React函数组件中如何匹配Fetch API返回的输入单词?
React函数组件打字游戏:解决Fetch内容与输入匹配问题
核心问题分析
- 数据结构误用:你调用的借口API返回的是包含完整句子的对象数组,原代码直接将整个数组存入
word状态,但实际需要的是句子拆分后的单个单词数组,导致匹配时拿对象和输入文本对比,必然不相等。 - 匹配逻辑错误:
checkMatch中word[currWordIndex]获取的是整个借口对象,而非单个单词,无法和输入框的文本做有效对比。 - 输入流程缺失:按下空格确认单词后,未清空输入框内容,导致后续输入叠加,影响下一次匹配。
- 计时逻辑隐患:使用
setTimeout在组件重渲染时可能创建多个定时器,导致计时加速。
修改后的完整代码
import React, { useState, useEffect, useRef } from "react"; function App() { const STARTING_TIME = 10; const [text, setText] = useState(""); const [timeRemaining, setTimeRemaining] = useState(STARTING_TIME); const [isTimeRunning, setIsTimeRunning] = useState(false); const [wordCount, setWordCount] = useState(0); const textBoxRef = useRef(null); const [currWordIndex, setCurrWordIndex] = useState(0); // 新增状态:存储拆分后的目标单词数组、当前显示的完整句子 const [targetWords, setTargetWords] = useState([]); const [currentSentence, setCurrentSentence] = useState(""); const intervalRef = useRef(null); // 存储定时器ID,用于清理 function handleChange(e) { const { value } = e.target; setText(value.trim()); // 自动去除输入的首尾空格 } function calculateWordCount(text) { const wordsArr = text.trim().split(" "); return wordsArr.filter((word) => word !== "").length; } const fetchData = async () => { const response = await fetch("https://excuser.herokuapp.com/v1/excuse"); const data = await response.json(); // 提取借口文本,拆分成单词数组 const excuseText = data[0].excuse; setCurrentSentence(excuseText); setTargetWords(excuseText.split(" ")); }; useEffect(() => { fetchData(); }, []); function startGame() { setIsTimeRunning(true); setTimeRemaining(STARTING_TIME); setText(""); setCurrWordIndex(0); setWordCount(0); textBoxRef.current.disabled = false; textBoxRef.current.focus(); // 启动定时器 intervalRef.current = setInterval(() => { setTimeRemaining(prev => { if (prev <= 1) { clearInterval(intervalRef.current); endGame(); return 0; } return prev - 1; }); }, 1000); } function endGame() { setIsTimeRunning(false); textBoxRef.current.disabled = true; } // 组件卸载时清理定时器 useEffect(() => { return () => { if (intervalRef.current) { clearInterval(intervalRef.current); } }; }, []); function handleKeyDown(e) { if (e.keyCode === 32 && isTimeRunning) { e.preventDefault(); // 阻止空格默认添加行为 checkMatch(); } } function checkMatch() { const currentTargetWord = targetWords[currWordIndex]; if (!currentTargetWord) return; // 已经完成所有单词匹配 const doesItMatch = currentTargetWord === text; if (doesItMatch) { setWordCount(prev => prev + 1); // 移动到下一个单词,若已到末尾则提前结束游戏 if (currWordIndex >= targetWords.length - 1) { endGame(); } else { setCurrWordIndex(prev => prev + 1); } } // 无论匹配成功与否,清空输入框 setText(""); textBoxRef.current.focus(); } // 高亮当前需要输入的单词 const renderSentence = () => { return currentSentence.split(" ").map((word, index) => ( <span key={index} style={{ color: index === currWordIndex ? "blue" : "black", fontWeight: index === currWordIndex ? "bold" : "normal" }} > {word}{" "} </span> )); }; return ( <div style={{ padding: "20px", maxWidth: "600px", margin: "0 auto" }}> <h1>打字速度测试</h1> <h3>请输入下方句子:</h3> <p style={{ fontSize: "18px", lineHeight: "1.5" }}> {renderSentence()} </p> <textarea ref={textBoxRef} onKeyDown={handleKeyDown} onChange={handleChange} value={text} disabled={!isTimeRunning} style={{ width: "100%", height: "80px", fontSize: "16px", padding: "10px" }} placeholder="点击开始后输入单词,按空格确认" /> <h4>剩余时间:{timeRemaining}秒</h4> <button onClick={startGame} disabled={isTimeRunning} style={{ padding: "8px 16px", fontSize: "16px", cursor: "pointer" }} > 开始游戏 </button> <h2>正确输入单词数:{wordCount}</h2> </div> ); } export default App;
关键修改说明
- 数据处理优化:从API返回的借口对象中提取文本,拆分成单词数组存入
targetWords,同时保留完整句子用于显示。 - 匹配逻辑修正:直接对比输入文本和
targetWords中对应索引的单词,匹配成功后累加正确数。 - 输入流程完善:按下空格后无论匹配成功与否,清空输入框并重新聚焦,确保下一次输入干净。
- 计时逻辑优化:使用
setInterval配合useRef存储定时器ID,避免重复创建定时器,组件卸载时自动清理。 - 用户体验提升:高亮当前需要输入的单词,让用户更清晰看到目标。
内容的提问来源于stack exchange,提问作者bigmouse
相关产品推荐
相关产品推荐

