Nghe cách nhấn phím cho tài liệu trong reactjs


82

Tôi muốn liên kết để đóng cửa sổ bật lên khởi động phản ứng hoạt động khi escapenhấn. Đây là mã

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

Nhưng không có gì được đăng nhập vào bảng điều khiển khi tôi nhấn bất kỳ phím nào. Tôi đã cố gắng nghe điều đó trên window và với các trường hợp khác nhau .'keypress ',' keyup ', v.v. nhưng có vẻ như tôi đang làm sai điều gì đó.


Đối với những gì nó có giá trị tôi đã xuất bản một lib KeyDown cho Phản ứng đó có nghĩa là để làm cho tất cả những điều này dễ dàng hơn nhiều: github.com/jedverity/react-keydown
glOrtho

Câu trả lời:


61

Bạn nên sử dụng keydownvà không keypress.

Nhấn phím (không được dùng nữa) thường chỉ được sử dụng cho các phím tạo ra đầu ra ký tự theo tài liệu

Nhấn phím (không dùng nữa)

Sự kiện nhấn phím được kích hoạt khi một phím được nhấn xuống và phím đó thường tạo ra giá trị ký tự

Keydown

Sự kiện nhấn phím xuống được kích hoạt khi nhấn một phím.


1
nhấn phím đã không được dùng nữa.
TimeParadox

49

Tôi vừa gặp vấn đề tương tự với điều này. Tôi sẽ sử dụng mã của bạn để minh họa cách khắc phục.

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

Vì bạn đang sử dụng cách thức createClass để thực hiện công việc, bạn không cần phải liên kết với một số phương thức nhất định như thislà ẩn trong mỗi phương thức được xác định.

Có một jsfiddle đang hoạt động, sử dụng phương thức createClass để tạo thành phần React ở đây.


9
Điều này sẽ không loại bỏ đúng trình xử lý sự kiện do ràng buộc cung cấp một phiên bản mới mỗi lần. Đảm bảo rằng bạn lưu vào bộ nhớ cache các kết quả ràng buộc trả về để thêm và xóa khỏi tài liệu một cách chính xác
Steven10172

@ Steven10172 Điểm tốt, vì hàm tạo không thực sự được định nghĩa trong phương thức React.createClass, bạn luôn có thể liên kết trong getInitialState ().
Chris Sullivan,

Liên quan đến các nhận xét ở trên, đây là một ví dụ hay về nơi liên kết và sử dụng trình nghe sự kiện stackoverflow.com/questions/32553158/…
Craig Myles

1
Lưu ý rằng nó componentWillMountđã không được chấp nhận kể từ React 16.3. IMO thay vào đó bạn nên đăng ký người nghe sự kiện componentDidMount.
Igor Akkerman

20

Nếu bạn có thể sử dụng React Hooks, thì một cách tiếp cận tốt là làm useEffectnhư vậy, người nghe sự kiện sẽ chỉ được đăng ký một lần và được hủy đăng ký đúng cách khi thành phần được ngắt kết nối.

Ví dụ dưới đây được trích xuất từ 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
  );
};

Ví dụ: bạn cũng có thể cài đặt nó từ npm npm i @use-it/event-listener- xem dự án tại đây - https://github.com/donavon/use-event-listener .

Sau đó, để sử dụng nó trong thành phần của bạn, bạn chỉ cần gọi nó bên trong thành phần chức năng của bạn, truyền tên sự kiện và trình xử lý. Ví dụ: nếu bạn muốn console.logmỗi khi nhấn phím 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>;
}

nếu Ứng dụng không phải là một thành phần chức năng, không thể sử dụng nó
ashubuntu

Cảm ơn bạn đã đăng bài này, đã giúp tôi khắc phục sự cố rò rỉ bộ nhớ lớn trong trình xử lý bàn phím toàn cầu của tôi. FWIW, hiệu ứng "lưu người nghe vào một ref" thực sự là chìa khóa - không chuyển các trình xử lý sự kiện của bạn trong useEffectmảng phụ thuộc mà thêm chúng vào document.body.onKeyDown!
aendrew

@aendrew: Sự khác biệt giữa lưu trình xử lý thành ref và chỉ khai báo một hàm?
thelonglqd

@thelonglqd Tôi nghĩ vì nếu không thì họ sẽ được thêm vào làm trình xử lý sự kiện nhiều lần - mặc dù vậy, đừng trích dẫn tôi về điều đó, đó là hơn nửa năm trước và trí nhớ của tôi bị mờ !!
aendrew

2

Một phiên bản của câu trả lời của Jt oso phù hợp hơn với câu hỏi này. Tôi nghĩ điều này đơn giản hơn nhiều so với các câu trả lời khác sử dụng thư viện bên ngoài hoặc các móc API để ràng buộc / hủy liên kết trình nghe.

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

3
Mục phải được tập trung đầu tiên. Nếu bạn muốn có trình xử lý sự kiện toàn cục, nó có thể không được kích hoạt vì ban đầu phần tử body được tập trung.
n1ru4l

1

Tôi có các yêu cầu tương tự đối với một div có thể tab.

Đoạn mã sau đối với tôi nằm trong lệnh gọi tới items.map ((item) => ...

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

Điều này đã làm việc cho tôi!


1

Tôi muốn có những người nghe sự kiện toàn cầu và có những hành vi kỳ lạ do sử dụng Cổng phản ứng. Sự kiện vẫn được kích hoạt trên phần tử tài liệu mặc dù đã bị hủy trên một thành phần phương thức cổng trong tài liệu.

Tôi đã hướng tới việc chỉ sử dụng trình nghe sự kiện trên một đối tượng gốc bao bọc toàn bộ cây thành phần. Vấn đề ở đây là ban đầu phần thân được lấy tiêu điểm chứ không phải phần tử gốc, do đó, các sự kiện đầu tiên sẽ được kích hoạt khi bạn tập trung một phần tử trong cây.

Giải pháp tôi đã chọn là thêm tabindex và tự động lấy nét nó bằng một móc hiệu ứng.

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>
};
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.