在 reactjs 中收听文档的 keypress 命令

我想绑定到关闭 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);
},

但是当我按任何键时,控制台中没有记录任何东西。我也试着在窗口和不同的情况下“按键”,“关键字”等,但似乎我做错了什么。

153868 次浏览

You should use keydown and not keypress.

Keypress (deprecated) is usually used only for keys that produce a character output as per the docs

Keypress (deprecated)

The keypress event is fired when a key is pressed down and that key normally produces a character value

Keydown

The keydown event is fired when a key is pressed down.

Just had a similar problem with this myself. I'll use your code to illustrate a fix.

// 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);
},

Since you are using the createClass way of doing things, you do not need to bind to certain methods as this is implicit in each method defined.

There is a working jsfiddle, using the createClass method of React component creation here.

I had the same requirements for a div that was tab-able.

The following code for me was inside of a call to items.map((item)=> ...

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

This worked for me!

If you can use React Hooks, a good approach is to useEffect, so the event listener will be subscribed only once and properly unsubscribed when the component is unmounted.

The example below was extracted from 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
);
};

You also could install it from npm, for example, npm i @use-it/event-listener - see the project here - https://github.com/donavon/use-event-listener.

Then, to use it in your component you just have to call it inside your functional component passing the event name and the handler. For example, if you want to console.log every time the Escape key is pressed:

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>;
}

A version of Jt oso's answer that is more relevant to this question. I think this is much simpler than the other answers that use external libraries or API hooks to bind/unbind the listener.

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

I wanted to have global event listeners and had weird behavior due to using React Portals. The event was still triggered on the document element despite being canceled on a portal modal component within the document.

I moved towards only using event listeners on a root object that wraps the whole component tree. The problem here was that initially the body is focused and not the root element, thus the events would first be fired once you focus an element within the tree.

The solution I went for is adding a tabindex and automatically focusing it with an effect hook.

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>
};