Слушайте нажатие клавиши для документа в Reactjs


86

Я хочу привязать, чтобы закрыть всплывающее окно загрузки активного ответа при 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' и т. Д., Но похоже, что я делаю что-то не так.


Как бы то ни было, я опубликовал
библиотеку keydown

Ответы:


63

Вы должны использовать keydownи нет keypress.

Нажатие клавиши (устарело) обычно используется только для клавиш, которые производят символьный вывод в соответствии с документами

Keypress (устарело)

Событие нажатия клавиши запускается, когда клавиша нажата, и эта клавиша обычно производит символьное значение

Keydown

Событие keydown запускается при нажатии клавиши.


1
keypress устарел.
TimeParadox

52

У меня была аналогичная проблема с этим. Я буду использовать ваш код, чтобы проиллюстрировать исправление.

// 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, использующий метод createClass для создания компонента React .


9
Это не приведет к правильному удалению прослушивателя событий, так как привязка каждый раз дает новый экземпляр. Убедитесь, что вы кэшируете результаты, которые возвращает привязка для правильного добавления и удаления из документа
Steven10172

@ Steven10172 Хороший момент, поскольку конструктор на самом деле не определен в методе React.createClass, вы всегда можете выполнить привязку в getInitialState ().
Крис Салливан

Что касается комментариев выше, это хороший пример того, где можно привязать и использовать прослушиватели событий stackoverflow.com/questions/32553158/…
Крейг Майлз

1
Обратите внимание, что componentWillMountэто устарело с React 16.3. ИМО вам следует вместо этого зарегистрировать слушателей событий componentDidMount.
Игорь Аккерман

24

Если вы можете использовать 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

1
Спасибо, что разместили это, помог мне исправить огромную утечку памяти в моих глобальных обработчиках клавиатуры. FWIW, эффект «сохранения слушателей ref» действительно является ключевым - не передавайте обработчики событий в useEffectмассив зависимостей, в который они добавляются document.body.onKeyDown!
Эндрю 02

@aendrew: Какая разница от сохранения обработчика в ссылку и простого объявления функции?
thelonglqd

@thelonglqd Я думаю, потому что в противном случае они будут добавлены в качестве обработчиков событий несколько раз - не цитируйте меня, хотя это было более полугода назад и моя память туманна !!
Эндрю

2

Версия ответа Jt oso, более подходящая для этого вопроса. Я думаю, что это намного проще, чем другие ответы, в которых используются внешние библиотеки или перехватчики API для привязки / отмены привязки слушателя.

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

4
В первую очередь нужно сфокусировать предмет. Если вы хотите иметь глобальный прослушиватель событий, он может не сработать, потому что изначально сфокусирован элемент body.
n1ru4l

Вы действительно можете использоватьif (event.key === 'Escape')
Ифань Ай

1

У меня были те же требования к div с вкладками.

Следующий код для меня был внутри вызова items.map ((item) => ...

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

Это сработало для меня!


1

Я хотел иметь глобальные прослушиватели событий, и у меня было странное поведение из-за использования React Portals. Событие все еще было инициировано для элемента документа, несмотря на то, что оно было отменено для модального компонента портала в документе.

Я перешел к использованию прослушивателей событий только для корневого объекта, который охватывает все дерево компонентов. Проблема заключалась в том, что изначально сфокусировано тело, а не корневой элемент, поэтому события сначала запускались, как только вы фокусируете элемент в дереве.

Решение, которое я выбрал, - это добавить 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>
};
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.