Слушайте нажатие клавиши для документа в 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.