在reactjs中收听按键文档


82

我想绑定以在escape按下时关闭活动的反应引导弹出窗口。这是代码

_handleEscKey:function(event){
         console.log(event);
        if(event.keyCode == 27){
          this.state.activePopover.hide();
        }
   },

  componentWillMount:function(){
     BannerDataStore.addChangeListener(this._onchange);
     document.addEventListener("click", this._handleDocumentClick, false);
     document.addEventListener("keyPress", this._handleEscKey, false);
   },


   componentWillUnmount: function() {
     BannerDataStore.removeChangeListener(this._onchange);
      document.removeEventListener("click", this._handleDocumentClick, false);
      document.removeEventListener("keyPress", this._handleEscKey, false);
   },

但是,当我按任意键时,控制台中都不会记录任何内容。我也尝试在窗口上以及不同情况下使用'keypress','keyup'等来收听,但似乎我做错了。


我已经发布了React的keydown库,以使所有这些事情变得更加简单:github.com/jedverity/react-keydown
glortho

Answers:


61

您应该使用keydown而不是keypress

Keypress(不建议使用)通常仅用于根据文档产生字符输出的键

按键(已弃用)

当按下某个键时会触发keypress事件,并且该键通常会产生一个字符值

按键

按下键时会触发keydown事件。


1
不推荐使用keypress。
TimeParadox

49

我自己也有类似的问题。我将使用您的代码来说明解决方法。

// for other devs who might not know keyCodes
var ESCAPE_KEY = 27;

_handleKeyDown = (event) => {
    switch( event.keyCode ) {
        case ESCAPE_KEY:
            this.state.activePopover.hide();
            break;
        default: 
            break;
    }
},

// componentWillMount deprecated in React 16.3
componentDidMount(){
    BannerDataStore.addChangeListener(this._onchange);
    document.addEventListener("click", this._handleDocumentClick, false);
    document.addEventListener("keydown", this._handleKeyDown);
},


componentWillUnmount() {
    BannerDataStore.removeChangeListener(this._onchange);
    document.removeEventListener("click", this._handleDocumentClick, false);
    document.removeEventListener("keydown", this._handleKeyDown);
},

由于您使用的是createClass的处事方式,因此无需绑定到某些方法,因为this每个方法都已定义。

有一个可用的jsfiddle,在这里使用React组件创建的createClass方法


9
由于每次绑定都会给新实例,因此这将无法正确删除事件侦听器。确保您缓存绑定返回的结果以正确添加和删除文档
Steven10172,17年

@ Steven10172好点,由于在React.createClass方法中并未真正定义构造函数,因此您始终可以在getInitialState()中进行绑定。
克里斯·沙利文

关于上述评论,这是在哪里绑定和使用事件监听器一个很好的例子stackoverflow.com/questions/32553158/...
克雷格·迈尔斯

1
请注意,componentWillMount从React 16.3开始已弃用。IMO,您应该改为在中注册事件侦听器componentDidMount
伊戈尔·阿克曼

20

如果您可以使用React Hooks,一个很好的方法是useEffect,因此事件侦听器将仅被订阅一次,并且在卸载组件时将被正确取消订阅。

以下示例摘自https://usehooks.com/useEventListener/

// Hook
function useEventListener(eventName, handler, element = window){
  // Create a ref that stores handler
  const savedHandler = useRef();

  // Update ref.current value if handler changes.
  // This allows our effect below to always get latest handler ...
  // ... without us needing to pass it in effect deps array ...
  // ... and potentially cause effect to re-run every render.
  useEffect(() => {
    savedHandler.current = handler;
  }, [handler]);

  useEffect(
    () => {
      // Make sure element supports addEventListener
      // On 
      const isSupported = element && element.addEventListener;
      if (!isSupported) return;

      // Create event listener that calls handler function stored in ref
      const eventListener = event => savedHandler.current(event);

      // Add event listener
      element.addEventListener(eventName, eventListener);

      // Remove event listener on cleanup
      return () => {
        element.removeEventListener(eventName, eventListener);
      };
    },
    [eventName, element] // Re-run if eventName or element changes
  );
};

您也可以从npm安装它,例如npm i @use-it/event-listener-在此处查看项目-https : //github.com/donavon/use-event-listener

然后,要在组件中使用它,只需在功能组件中调用它,并传递事件名称和处理程序。例如,如果您想console.log每次按Escape键:

import useEventListener from '@use-it/event-listener'

const ESCAPE_KEYS = ['27', 'Escape'];

const App = () => {
  function handler({ key }) {
    if (ESCAPE_KEYS.includes(String(key))) {
      console.log('Escape key pressed!');
    }
  }

  useEventListener('keydown', handler);

  return <span>hello world</span>;
}

如果应用不是功能组件,则不能使用它
ashubuntu

感谢您发布此信息,它帮助我修复了全局键盘处理程序中的大量内存泄漏。FWIW,“将侦听器保存为ref”效果确实很关键-请勿在将事件处理程序useEffect添加到的事件数组中传递事件处理程序document.body.onKeyDown
aendrew

@aendrew:从保存处理程序到引用并仅声明一个函数有什么区别?
thelonglqd

@thelonglqd我认为是因为否则它们会被多次添加为事件处理程序,但是请不要引用我,那是半年多以前的,我的记忆模糊!
aendrew

2

Jt oso的答案版本与此问题更相关。我认为这比使用外部库或API挂钩来绑定/取消绑定侦听器的其他答案要简单得多。

var KEY_ESCAPE = 27;
...
    function handleKeyDown(event) {
        if (event.keyCode === KEY_ESCAPE) {
            /* do your action here */
        }
    }
...
    <div onKeyDown={handleKeyDown}>
...

3
必须首先关注该项目。如果要具有全局事件侦听器,则可能不会触发该事件,因为最初body元素已聚焦。
n1ru4l

1

对于可制表的div,我有相同的要求。

以下代码对我来说是在对items.map((item)=> ...

  <div
    tabindex="0"
    onClick={()=> update(item.id)}
    onKeyDown={()=> update(item.id)}
   >
      {renderItem(item)}
  </div>

这对我有用!


1

我想拥有全局事件侦听器,并且由于使用React Portal而具有奇怪的行为。尽管已在文档中的门户网站模式组件上取消了事件,但仍在文档元素上触发了该事件。

我转向只在包装整个组件树的根对象上使用事件侦听器。这里的问题是,最初,主体是聚焦的,而不是根元素,因此一旦在树中聚焦某个元素,就会首先触发事件。

我寻求的解决方案是添加一个tabindex并使用效果挂钩自动将其聚焦。

import React from "react";

export default GlobalEventContainer = ({ children, ...props }) => {
  const rootRef = React.useRef(null);
  useEffect(() => {
    if (document.activeElement === document.body && rootContainer.current) 
      rootContainer.current.focus();
    }
  });

  return <div {...props} tabIndex="0" ref={rootRef}>{children}</div>
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.