Прослухайте натискання клавіші для документа в responsejs


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' тощо, але, здається, я роблю щось не так.


Для того, що варто, я опублікував бібліотеку клавіатури для React, яка має зробити все це набагато простішою: github.com/jedverity/react-keydown
glortho

Відповіді:


63

Вам слід використовувати, keydownа ні keypress.

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

Натискання клавіш (застаріле)

Подія натискання клавіші спрацьовує при натисканні клавіші, і ця клавіша зазвичай видає значення символу

Клавіатура

Подія зниження клавіш спрацьовує при натисканні клавіші.


1
натискання клавіш застаріло.
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 Хороший момент, оскільки конструктор насправді не визначений у методі 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!
aendrew 02.03.20

@aendrew: Яка різниця від збереження обробника до посилання та просто оголошення функції?
thelonglqd

@thelonglqd Я думаю, бо в іншому випадку їх додають як обробників подій кілька разів - не цитуй мене, хоча це було більше півроку тому, і моя пам’ять туманна !!
aendrew

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')
Yifan Ai

1

У мене були однакові вимоги до div, який можна було вкладати.

Наступний код для мене був всередині виклику items.map ((item) => ...

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

Це спрацювало для мене!


1

Я хотів мати глобальних слухачів подій і мав дивну поведінку завдяки використанню порталів React. Подія все ще була ініційована для елемента документа, незважаючи на те, що скасована на модальному компоненті порталу в документі.

Я перейшов до використання лише прослуховувачів подій на кореневому об’єкті, який обгортає все дерево компонентів. Проблема тут полягала в тому, що спочатку тіло фокусується, а не кореневий елемент, отже, події спочатку запускатимуться, коли ви фокусуєте елемент у дереві.

Рішення, для якого я пішов, - це додавання табіндексу та автоматичне фокусування його за допомогою гачка ефектів.

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.