React Hooks 的 Keydown/up 事件无法正常工作

tho*_*nas 4 javascript keyboard-events reactjs react-hooks

我正在尝试为我正在开发的游戏创建基于箭头的键盘控件。当然,我正在努力与 React 保持同步,所以我想创建一个函数组件并使用钩子。我为我的错误组件创建了一个JSFiddle

它几乎按预期工作,除非我同时按下很多箭头键。然后似乎keyup没有触发某些事件。也可能是“状态”未正确更新。

我喜欢这样:

  const ALLOWED_KEYS = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']
  const [pressed, setPressed] = React.useState([])

  const handleKeyDown = React.useCallback(event => {
    const { key } = event
    if (ALLOWED_KEYS.includes(key) && !pressed.includes(key)) {
      setPressed([...pressed, key])
    }
  }, [pressed])

  const handleKeyUp = React.useCallback(event => {
    const { key } = event
    setPressed(pressed.filter(k => k !== key))
  }, [pressed])

  React.useEffect(() => {
    document.addEventListener('keydown', handleKeyDown)
    document.addEventListener('keyup', handleKeyUp)

    return () => {
      document.removeEventListener('keydown', handleKeyDown)
      document.removeEventListener('keyup', handleKeyUp)
    }
  })
Run Code Online (Sandbox Code Playgroud)

我的想法是我做对了,但是作为钩子的新手,很可能这就是问题所在。特别是因为我重新创建了与基于类的组件相同的组件:https : //jsfiddle.net/vus4nrfe/

这似乎工作正常......

nor*_*ial 6

有 3 个关键的事情要做才能使它像您的类组件一样按预期工作。

正如其他人提到的,useEffect您需要添加一个[]作为依赖项数组,该数组只会触发一次addEventLister函数。

第二个主要问题是您没有像在类组件中那样改变函数组件中pressed数组的先前状态,如下所示:

// onKeyDown event
this.setState(prevState => ({
   pressed: [...prevState.pressed, key],
}))

// onKeyUp event
this.setState(prevState => ({
   pressed: prevState.pressed.filter(k => k !== key),
}))
Run Code Online (Sandbox Code Playgroud)

您需要更新功能一如下:

// onKeyDown event
setPressedKeys(previousPressedKeys => [...previousPressedKeys, key]);

// onKeyUp event
setPressedKeys(previousPressedKeys => previousPressedKeys.filter(k => k !== key));
Run Code Online (Sandbox Code Playgroud)

第三件事是onKeyDownonKeyUp事件的定义已经移到了里面,useEffect所以你不需要使用useCallback.

提到的事情解决了我的问题。请找到我制作的以下工作 GitHub 存储库,它按预期工作:

https://github.com/norbitrial/react-keydown-useeffect-componentdidmount

如果您更喜欢,请在此处找到可用的 JSFiddle 版本:

https://jsfiddle.net/0aogqbyp/

存储库中的基本部分,完全工作的组件:

const KeyDownFunctional = () => {
    const [pressedKeys, setPressedKeys] = useState([]);

    useEffect(() => {
        const onKeyDown = ({key}) => {
            if (Consts.ALLOWED_KEYS.includes(key) && !pressedKeys.includes(key)) {
                setPressedKeys(previousPressedKeys => [...previousPressedKeys, key]);
            }
        }

        const onKeyUp = ({key}) => {
            if (Consts.ALLOWED_KEYS.includes(key)) {
                setPressedKeys(previousPressedKeys => previousPressedKeys.filter(k => k !== key));
            }
        }

        document.addEventListener('keydown', onKeyDown);
        document.addEventListener('keyup', onKeyUp);

        return () => {
            document.removeEventListener('keydown', onKeyDown);
            document.removeEventListener('keyup', onKeyUp);
        }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    return <>
        <h3>KeyDown Functional Component</h3>
        <h4>Pressed Keys:</h4>

        {pressedKeys.map(e => <span key={e} className="key">{e}</span>)}
    </>
}
Run Code Online (Sandbox Code Playgroud)

我使用// eslint-disable-next-line react-hooks/exhaustive-depsfor 的原因useEffect是因为一旦pressedorpressedKeys数组发生变化,我不想每次都重新附加事件。

我希望这有帮助!

  • 啊哈,有道理!另一件事:我注意到很多不必要的重新渲染。似乎“onKeyDown”处理程序中的“!pressedKeys.includes(key)”不起作用,因为“pressedKeys”值未更新。我认为这是因为 `pressedKeys` 不在 `useEffect` 的依赖项数组中,对吧? (2认同)