You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React函数组件中如何匹配Fetch API返回的输入单词?

React函数组件打字游戏:解决Fetch内容与输入匹配问题

核心问题分析

  1. 数据结构误用:你调用的借口API返回的是包含完整句子的对象数组,原代码直接将整个数组存入word状态,但实际需要的是句子拆分后的单个单词数组,导致匹配时拿对象和输入文本对比,必然不相等。
  2. 匹配逻辑错误:checkMatch中word[currWordIndex]获取的是整个借口对象,而非单个单词,无法和输入框的文本做有效对比。
  3. 输入流程缺失:按下空格确认单词后,未清空输入框内容,导致后续输入叠加,影响下一次匹配。
  4. 计时逻辑隐患:使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 11:15:32