reactjsでドキュメントのキープレスを聞く


86

escapeプレス時にアクティブなreactブートストラップポップオーバーを閉じるためにバインドしたいです。コードは次のとおりです。

_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ます。

Keypress(非推奨)は通常、ドキュメントに従って文字出力を生成するキーにのみ使用されます

キープレス(非推奨)

keypressイベントは、キーが押されたときに発生し、そのキーは通常、文字値を生成します

キーダウン

キーダウンイベントは、キーが押されたときに発生します。


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定義された各メソッドに暗黙的に含まれているように、特定のメソッドにバインドする必要はありません。

ここにReactコンポーネント作成のcreateClassメソッドを使用する動作するjsfiddleがあります。


9
バインドが毎回新しいインスタンスを与えるため、これはイベントリスナーを適切に削除しません。ドキュメントに適切に追加および削除するために、バインドが返す結果をキャッシュするようにしてください
Steven10172 2017年

@ Steven10172良い点です。コンストラクターはReact.createClassメソッドで実際には定義されていないため、いつでもgetInitialState()でバインドできます。
クリス・サリバン

上記のコメントに関連して、これはイベントリスナーをバインドして使用する場所の良い例ですstackoverflow.com/questions/32553158/…–
Craig Myles

1
componentWillMountReact16.3で非推奨になっていることに注意してください。IMOでは、代わりにイベントリスナーをに登録する必要がありますcomponentDidMount
IgorAkkerman19年

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、Escキーが押されるたびに実行する場合は、次のようにします。

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

@aendrew:ハンドラーをrefに保存し、関数を宣言することとの違いは何ですか?
thelonglqd

@thelonglqdそうしないと、イベントハンドラーとして複数回追加されるためだと思いますが、それについては引用しないでください。それは半年以上前のことであり、私の記憶は曇っています。
aendrew

2

この質問により関連性のあるJtosoの回答のバージョン。これは、外部ライブラリまたは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')
YifanAi20年

1

タブ対応のdivにも同じ要件がありました。

次のコードは、items.map((item)=>..。への呼び出しの中にありました。

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

これは私のために働いた!


1

グローバルなイベントリスナーが欲しかったのですが、Reactポータルを使用しているために奇妙な動作をしていました。ドキュメント内のポータルモーダルコンポーネントでキャンセルされたにもかかわらず、イベントはドキュメント要素でトリガーされました。

コンポーネントツリー全体をラップするルートオブジェクトでイベントリスナーのみを使用するようになりました。ここでの問題は、最初はルート要素ではなくボディに焦点が合っているため、ツリー内の要素に焦点を合わせると、最初にイベントが発生することでした。

私が行った解決策は、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>
};
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.