React-Redux:绑定按键操作以启动 reducer 序列的规范方法是什么?

React-Redux: What is the canonical way to bind a keypress action to kick off a reducer sequence?

这是一个关于 react-redux 的新手问题,我花了几个小时四处寻找才找到,所以我发布了这个问题,然后为后代回答,也可能是代码审查。

我正在使用 react-redux 创建一个游戏,我想在其中使用 WASD 键在小地图上移动角色。 (这只是一个更大努力的实践示例)。该地图仅由一堆彩色 <div> 组成。

据我所知,我需要以某种方式将按键事件绑定到 React DOM 中的某些内容,以便触发 mapDispatchToProps 然后开始重新评估减速机。问题是,这是一个按键,没有什么可以绑定的。我正在使用 jquery 绑定按键并调用函数。

相关查询:

解决方案改编自此处:

关键是删除 jquery 并使用 document.addEventListener 将其绑定到反应组件中。这是工作代码的摘录:

////////////////////////////////////////////
////////////////////// containers
////////////////////////////////////////////
class GameMap extends React.Component{
  renderMap(){
    console.log('renderMap')
    console.log(this.props)
    return this.props.gamemap.map((tile) => {
      //const x = "tile " + tile
      return <div className={"tile " + tile}></div>
    })
  }
  render() {
    console.log('GameMap.render()')
    return (
      <div className="GameMap">
        {this.renderMap()}
      </div>)
  } 
  componentDidMount() {
    console.log("componentDidMount")
    console.log(this)
    // the following line won't be bound to the store here...
    document.addEventListener("keydown", this.props.keyPress );
  }
}
function GMmapStateToProps(state){
  //from here goes into this.props
  console.log('BLmapStateToProps')
  console.log(state)
  const gamemap = state.gamemap.gamemap.map((a) => {
    switch (a){
      case 1:
        return "tile-free"
      case 9:
        return "tile-user"
             }
    return "tile-wall"
  })
  return{
    gamemap: gamemap
  }
}
function GMmapDispatchToProps(dispatch){
  //when selectbook called, pass result to all reducers
  console.log('GMmapDispatchToProps')
  return bindActionCreators({keyPress: keyPress}, dispatch)
}
const VGameMap = connect(GMmapStateToProps, GMmapDispatchToProps)(GameMap)

////////////////////////////////////////////
////////////////////// actions
////////////////////////////////////////////
// actions/index.js action creator
function keyPress(key) {
  console.log('keyPress: ', key)
  console.log(key.key)
  var vector = ""
  switch(key){
    case 'w', 'ArrowUp':
      vector = {x:0,y:1}
    case 's', 'ArrowDown':
      vector = {x:0,y:-1}
    case 'a', 'ArrowLeft':
      vector = {x:-1,y:0}
    case 'd', 'ArrowRight':
      vector = {x:1,y:0}
            }
  return {
    type: "KEYPRESS",
    payload: vector
  } // this is an action created
}

您基本上可以在 keypress 事件处理程序中触发一个动作

class App extends React.Component {

  constructor() {
    super();
    this.handleKeyPress = this.handleKeyPress.bind(this);
  }

  handleKeyPress(event) {
    // you may also add a filter here to skip keys, that do not have an effect for your app
    this.props.keyPressAction(event.keyCode);
  }

  componentDidMount() {
     document.addEventListener('keypress', this.handleKeyPress);
  }

  componentWillUnmount() {
     document.removeEventListener('keypress', this.handleKeyPress);
  }

  render() {
    return <div>Your game content</div>;
  }
}

export default connect(mapStateToProps, {keyPressAction})(App)

handleKeyPress 调用 action creator,它将 action 向下推送到 reducer。

对于那些在带有函数 类 的打字稿中使用较新的 redux 的人,您可能需要这样的东西:

import { useDispatch } from "react-redux";
import { useEffect } from "react";

const App = () => {
  const dispatch = useDispatch();

  useEffect(() => {
    document.addEventListener('keydown', handleKeyDown);
    document.addEventListener('keyup', handleKeyUp);
    return function cleanup() {
      document.removeEventListener('keydown', handleKeyDown);
      document.removeEventListener('keyup', handleKeyUp);
    };
  });

  const handleKeyDown = (event) => {
    console.debug("Key event", event);
    dispatch(handleKeyInput(game_state, connection_status, event.key));
    document.removeEventListener('keydown', handleKeyDown);
  };

  const handleKeyUp = (event) => {
    document.addEventListener('keydown', handleKeyDown, {once: true});
  };
}

https://reactjs.org/docs/hooks-effect.html