Odsłuchaj naciśnięcie klawisza dokumentu w Reactjs

86

Chcę powiązać, aby zamknąć aktywne okno wyskakujące bootstrap reagowania na escapeprasie. Oto kod

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

Ale nic się nie loguje w konsoli po naciśnięciu dowolnego klawisza. Próbowałem słuchać tego również w oknie iz różnymi przypadkami. „Keypress”, „keyup” itp., Ale wygląda na to, że robię coś nie tak.

zawsze się ucz
źródło
Z tego powodu opublikowałem bibliotekę keydown dla React, która ma to wszystko znacznie ułatwić: github.com/jedverity/react-keydown
glortho

Odpowiedzi:

63

Powinieneś używać, keydowna nie keypress.

Keypress (przestarzałe) jest zwykle używane tylko w przypadku klawiszy, które generują znak wyjściowy zgodnie z dokumentacją

Keypress (przestarzałe)

Zdarzenie keypress jest uruchamiane, gdy klawisz jest wciśnięty, a ten klawisz zwykle generuje wartość znakową

Keydown

Zdarzenie keydown jest uruchamiane po naciśnięciu klawisza.

Dhiraj
źródło
1
keypress został wycofany.
TimeParadox
52

Sam miałem z tym podobny problem. Użyję twojego kodu, aby zilustrować poprawkę.

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

Ponieważ używasz metody createClass do robienia rzeczy, nie musisz wiązać się z pewnymi metodami, co thisjest niejawne w każdej zdefiniowanej metodzie.

Istnieje działające jsfiddle, wykorzystujące metodę createClass do tworzenia komponentów React .

Chris Sullivan
źródło
9
Nie spowoduje to prawidłowego usunięcia detektora zdarzeń, ponieważ wiązanie za każdym razem daje nową instancję. Upewnij się, że buforujesz wyniki, które wiążą powroty, aby poprawnie dodawać i usuwać z dokumentu
Steven10172
@ Steven10172 Dobra uwaga, ponieważ konstruktor nie jest tak naprawdę zdefiniowany w metodzie React.createClass, zawsze możesz powiązać go w getInitialState ().
Chris Sullivan
W związku z powyższymi komentarzami jest to dobry przykład tego, gdzie można powiązać i używać detektorów zdarzeń stackoverflow.com/questions/32553158/ ...
Craig Myles
1
Zauważ, że componentWillMountw React 16.3 jest przestarzałe. IMO powinieneś zamiast tego zarejestrować detektory zdarzeń w componentDidMount.
Igor Akkerman
24

Jeśli możesz używać haków React, dobrym podejściem jest to useEffect, aby nasłuchiwacz zdarzeń był subskrybowany tylko raz i poprawnie anulowany, gdy komponent zostanie odmontowany.

Poniższy przykład został wyodrębniony z 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
  );
};

Można go też zainstalować npm z npm npm i @use-it/event-listener- zobacz projekt tutaj - https://github.com/donavon/use-event-listener .

Następnie, aby użyć go w swoim komponencie, wystarczy wywołać go wewnątrz komponentu funkcjonalnego, przekazując nazwę zdarzenia i procedurę obsługi. Na przykład, jeśli chcesz za console.logkażdym razem, gdy naciśniesz klawisz 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>;
}
Mate Paiva
źródło
jeśli aplikacja nie jest składnikiem funkcjonalnym, nie można jej używać
ashubuntu
1
Dzięki za opublikowanie tego, pomogło mi naprawić ogromny wyciek pamięci w moich globalnych programach obsługi klawiatury. FWIW, efekt "zapisz nasłuchiwanie w ref" jest naprawdę kluczowy - nie przekazuj swoich programów obsługi zdarzeń do useEffecttablicy zależności, do której je dodaje document.body.onKeyDown!
aendrew
@aendrew: Jaka jest różnica między zapisaniem programu obsługi do ref i po prostu zadeklarowaniem funkcji?
thelonglqd
@thelonglqd Myślę, że w przeciwnym razie są dodawane jako programy obsługi zdarzeń wiele razy - nie cytuj mnie jednak, to było ponad pół roku temu, a moja pamięć jest mglista !!
aendrew
2

Wersja odpowiedzi Jt oso, która jest bardziej odpowiednia dla tego pytania. Myślę, że jest to znacznie prostsze niż inne odpowiedzi, które używają zewnętrznych bibliotek lub haków API do łączenia / usuwania wiązania słuchacza.

var KEY_ESCAPE = 27;
...
    function handleKeyDown(event) {
        if (event.keyCode === KEY_ESCAPE) {
            /* do your action here */
        }
    }
...
    <div onKeyDown={handleKeyDown}>
...
Tongfa
źródło
4
Element musi być najpierw skupiony. Jeśli chcesz mieć globalny detektor zdarzeń, może on nie zostać wyzwolony, ponieważ początkowo skupiony jest element body.
n1ru4l
Możesz faktycznie użyćif (event.key === 'Escape')
Yifan Ai
1

Miałem te same wymagania co do elementu div, który można zakładać.

Poniższy kod znajdował się w wywołaniu items.map ((item) => ...

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

To zadziałało dla mnie!

Jt oso
źródło
1

Chciałem mieć globalnych odbiorników wydarzeń i miałem dziwne zachowanie z powodu korzystania z portali reagujących. Zdarzenie było nadal wyzwalane w elemencie dokumentu, mimo że zostało anulowane w module modalnym portalu w dokumencie.

Zacząłem używać tylko detektorów zdarzeń na obiekcie głównym, który otacza całe drzewo komponentów. Problem polegał na tym, że początkowo skupiono się na treści, a nie na elemencie głównym, dlatego zdarzenia byłyby najpierw uruchamiane, gdy skupisz się na elemencie w drzewie.

Rozwiązaniem, które wybrałem, jest dodanie tabindeksu i automatyczne ustawienie go za pomocą haka efektu.

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
źródło