Hören Sie den Tastendruck für das Dokument in Reaktion

82

Ich möchte binden, um das aktive React-Bootstrap-Popover beim escapeDrücken zu schließen. Hier ist der Code

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

Es wird jedoch nichts in der Konsole protokolliert, wenn ich eine Taste drücke. Ich habe versucht, das auch im Fenster und in verschiedenen Fällen zu hören. "Tastendruck", "Tastendruck" usw., aber es scheint, dass ich etwas falsch mache.

immer lernen
quelle
Für das, was es wert ist, habe ich eine Keydown-Bibliothek für React veröffentlicht, die all dies viel einfacher machen soll: github.com/jedverity/react-keydown
glortho

Antworten:

61

Sie sollten verwenden keydownund nicht keypress.

Tastendruck (veraltet) wird normalerweise nur für Tasten verwendet, die eine Zeichenausgabe gemäß den Dokumenten erzeugen

Tastendruck (veraltet)

Das Tastendruckereignis wird ausgelöst, wenn eine Taste gedrückt wird und diese Taste normalerweise einen Zeichenwert erzeugt

Taste nach unten

Das Keydown-Ereignis wird ausgelöst, wenn eine Taste gedrückt wird.

Dhiraj
quelle
1
Tastendruck ist veraltet.
TimeParadox
49

Hatte gerade ein ähnliches Problem damit. Ich werde Ihren Code verwenden, um eine Korrektur zu veranschaulichen.

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

Da Sie die Methode createClass verwenden, müssen Sie nicht an bestimmte Methoden binden, wie dies thisin jeder definierten Methode impliziert ist.

Es gibt eine funktionierende jsfiddle, die hier die createClass-Methode zur Erstellung von React-Komponenten verwendet .

Chris Sullivan
quelle
9
Dadurch wird der Ereignis-Listener nicht ordnungsgemäß entfernt, da durch die Bindung jedes Mal eine neue Instanz angegeben wird.
Stellen
@ Steven10172 Guter Punkt, da der Konstruktor in der React.createClass-Methode nicht wirklich definiert ist, können Sie immer in getInitialState () binden.
Chris Sullivan
In Bezug auf die obigen Kommentare ist dies ein schönes Beispiel dafür, wo die Ereignis-Listener gebunden
Craig Myles
1
Beachten Sie, dass componentWillMountdies ab Reaktion 16.3 veraltet ist. IMO sollten Sie stattdessen die Ereignis-Listener registrieren componentDidMount.
Igor Akkerman
20

Wenn Sie React Hooks verwenden können, ist dies ein guter Ansatz useEffect, sodass der Ereignis-Listener nur einmal abonniert und ordnungsgemäß abgemeldet wird, wenn die Komponente nicht bereitgestellt wird.

Das folgende Beispiel wurde aus https://usehooks.com/useEventListener/ extrahiert :

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

Sie können es auch von npm aus installieren, z. B. npm i @use-it/event-listener- siehe Projekt hier - https://github.com/donavon/use-event-listener .

Um es dann in Ihrer Komponente zu verwenden, müssen Sie es nur in Ihrer Funktionskomponente aufrufen und den Ereignisnamen und den Handler übergeben. Wenn Sie beispielsweise console.logjedes Mal möchten, wenn die Escape-Taste gedrückt wird:

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>;
}
Kumpel Paiva
quelle
Wenn die App keine funktionale Komponente ist, kann sie nicht verwendet werden
ashubuntu
Vielen Dank, dass Sie dies gepostet haben. Dies hat mir geholfen, einen massiven Speicherverlust in meinen globalen Tastaturhandlern zu beheben. FWIW, der refEffekt "Listener für einen speichern " ist wirklich wichtig - übergeben Sie Ihre Ereignishandler nicht in dem useEffectAbhängigkeitsarray, zu dem sie hinzugefügt werden document.body.onKeyDown!
Andrew
@aendrew: Was ist der Unterschied zwischen dem Speichern eines Handlers und einem Ref und dem Deklarieren einer Funktion?
lange
@thelonglqd Ich denke, weil sie sonst mehrmals als Event-Handler hinzugefügt werden - zitiere mich aber nicht dazu, das war vor über einem halben Jahr und mein Gedächtnis ist neblig !!
aendrew
2

Eine Version der Antwort von Jt oso, die für diese Frage relevanter ist. Ich denke, dies ist viel einfacher als die anderen Antworten, die externe Bibliotheken oder API-Hooks verwenden, um den Listener zu binden / zu lösen.

var KEY_ESCAPE = 27;
...
    function handleKeyDown(event) {
        if (event.keyCode === KEY_ESCAPE) {
            /* do your action here */
        }
    }
...
    <div onKeyDown={handleKeyDown}>
...
Tongfa
quelle
3
Der Gegenstand muss zuerst fokussiert werden. Wenn Sie einen globalen Ereignis-Listener haben möchten, wird dieser möglicherweise nicht ausgelöst, da das Body-Element anfangs fokussiert ist.
n1ru4l
1

Ich hatte die gleichen Anforderungen für ein Div, das tabulatorfähig war.

Der folgende Code war für mich Teil eines Aufrufs von items.map ((item) => ...

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

Das hat bei mir funktioniert!

Jt oso
quelle
1

Ich wollte globale Ereignis-Listener haben und hatte aufgrund der Verwendung von React Portals ein seltsames Verhalten. Das Ereignis wurde weiterhin für das Dokumentelement ausgelöst, obwohl es für eine modale Portalkomponente im Dokument abgebrochen wurde.

Ich habe nur Ereignis-Listener für ein Stammobjekt verwendet, das den gesamten Komponentenbaum umschließt. Das Problem hierbei war, dass anfangs der Körper fokussiert ist und nicht das Wurzelelement. Daher werden die Ereignisse zuerst ausgelöst, sobald Sie ein Element innerhalb des Baums fokussieren.

Die Lösung, für die ich mich entschieden habe, besteht darin, einen Tabindex hinzuzufügen und ihn automatisch mit einem Effekthaken zu fokussieren.

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