React打字测试组件仅接收首次输入后停止运行的问题排查
问题排查结果
你的代码存在4处逻辑错误,直接导致组件只能响应首次按键:
- React的
setState是异步批量更新的,你在keyDown回调里调用changeInput(e.key)后,立刻传入compareCharacters的input还是上一次渲染的旧值,首次触发时input是初始空字符串,根本拿不到刚按下的键值 - 直接对state存储的数组调用
splice属于直接修改原状态,React无法检测到这种可变数据的变更,不会触发正常重渲染;而且Array.splice返回值是被删除元素组成的数组,不是删除首元素后的剩余数组,第一次执行后lorem状态会变成仅包含第一个被删除字符的数组,长度为1,后续自然无法匹配更多内容 - 你同时维护了
position位置索引、每次截断数组两套匹配定位逻辑,二者重复很容易出现索引越界、匹配错位的问题 - 错误状态触发后没有重置逻辑,一旦输入错误就会永久保持错误样式
修复后代码
把重复的状态逻辑删掉,对比时直接使用事件对象返回的按键值,状态更新遵循不可变数据原则,修复后的代码如下:
import { useState } from "react" const Input = ({text}) => { // 原始字符数组不需要每次修改,仅用position指针标记当前待匹配位置即可 const [charList] = useState(text.split('')) const [position, setPosition] = useState(0) const [isError, setIsError] = useState(false) const handleKeyDown = (e) => { const pressedKey = e.key // 过滤Shift、Ctrl、退格等功能键,可根据自己的需求调整过滤规则 if (pressedKey.length !== 1) return if (pressedKey === charList[position]) { // 用函数式更新保证拿到最新的position值,避免连续按键时状态计算错误 setPosition(prev => prev + 1) setIsError(false) } else { setIsError(true) } } return ( <input className={`text ${isError ? 'new' : ''}`} type="text" size="10000" onKeyDown={handleKeyDown} autoFocus /> ) } export default Input
额外注意点
- 如果需要展示用户已输入的内容,可以单独维护一个输入值状态,但字符匹配逻辑不要依赖刚更新的state,直接取事件回调里拿到的
e.key即可,避免React闭包陷阱拿到旧值 - 所有基于原有state计算新state的场景,优先使用函数式更新写法,避免事件快速触发时状态更新不同步的问题
内容的提问来源于stack exchange,提问作者user19308961
相关产品推荐
相关产品推荐

