ReactJS - Thêm trình nghe sự kiện tùy chỉnh vào thành phần


87

Trong javascript cũ đơn giản, tôi có DIV

<div class="movie" id="my_movie">

và mã javascript sau

var myMovie = document.getElementById('my_movie');
myMovie.addEventListener('nv-enter', function (event) {
     console.log('change scope');
});

Bây giờ tôi có một Thành phần React, bên trong thành phần này, trong phương thức kết xuất, tôi đang trả về div của mình. Làm cách nào để thêm trình xử lý sự kiện cho sự kiện tùy chỉnh của tôi? (Tôi đang sử dụng thư viện này cho các ứng dụng TV - điều hướng )

import React, { Component } from 'react';

class MovieItem extends Component {

  render() {

    if(this.props.index === 0) {
      return (
        <div aria-nv-el aria-nv-el-current className="menu_item nv-default">
            <div className="indicator selected"></div>
            <div className="category">
                <span className="title">{this.props.movieItem.caption.toUpperCase()}</span>
            </div>
        </div>
      );
    }
    else {
      return (
        <div aria-nv-el className="menu_item nv-default">
            <div className="indicator selected"></div>
            <div className="category">
                <span className="title">{this.props.movieItem.caption.toUpperCase()}</span>
            </div>
        </div>
      );
    }
  }

}

export default MovieItem;

Cập nhật # 1:

nhập mô tả hình ảnh ở đây

Tôi đã áp dụng tất cả các ý tưởng được cung cấp trong các câu trả lời. Tôi đặt thư viện điều hướng thành chế độ gỡ lỗi và tôi có thể điều hướng trên các mục menu của mình chỉ dựa trên bàn phím (như bạn có thể thấy trong ảnh chụp màn hình, tôi có thể điều hướng đến Phim 4) nhưng khi tôi đặt tiêu điểm một mục trong menu hoặc nhấn enter, tôi không thấy bất kỳ thứ gì trong bảng điều khiển.

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class MenuItem extends Component {

  constructor(props) {
    super(props);
    // Pre-bind your event handler, or define it as a fat arrow in ES7/TS
    this.handleNVFocus = this.handleNVFocus.bind(this);
    this.handleNVEnter = this.handleNVEnter.bind(this);
    this.handleNVRight = this.handleNVRight.bind(this);
  }

  handleNVFocus = event => {
      console.log('Focused: ' + this.props.menuItem.caption.toUpperCase());
  }

  handleNVEnter = event => {
      console.log('Enter: ' + this.props.menuItem.caption.toUpperCase());
  }

  handleNVRight = event => {
      console.log('Right: ' + this.props.menuItem.caption.toUpperCase());
  }

  componentDidMount() {
    ReactDOM.findDOMNode(this).addEventListener('nv-focus', this.handleNVFocus);
    ReactDOM.findDOMNode(this).addEventListener('nv-enter', this.handleNVEnter);
    ReactDOM.findDOMNode(this).addEventListener('nv-right', this.handleNVEnter);
    //this.refs.nv.addEventListener('nv-focus', this.handleNVFocus);
    //this.refs.nv.addEventListener('nv-enter', this.handleNVEnter);
    //this.refs.nv.addEventListener('nv-right', this.handleNVEnter);
  }

  componentWillUnmount() {
    ReactDOM.findDOMNode(this).removeEventListener('nv-focus', this.handleNVFocus);
    ReactDOM.findDOMNode(this).removeEventListener('nv-enter', this.handleNVEnter);
    ReactDOM.findDOMNode(this).removeEventListener('nv-right', this.handleNVRight);
    //this.refs.nv.removeEventListener('nv-focus', this.handleNVFocus);
    //this.refs.nv.removeEventListener('nv-enter', this.handleNVEnter);
    //this.refs.nv.removeEventListener('nv-right', this.handleNVEnter);
  }

  render() {
    var attrs = this.props.index === 0 ? {"aria-nv-el-current": true} : {};
    return (
      <div ref="nv" aria-nv-el {...attrs} className="menu_item nv-default">
          <div className="indicator selected"></div>
          <div className="category">
              <span className="title">{this.props.menuItem.caption.toUpperCase()}</span>
          </div>
      </div>
    )
  }

}

export default MenuItem;

Tôi để lại một số dòng nhận xét vì trong cả hai trường hợp, tôi không thể đăng nhập các dòng bảng điều khiển.

Cập nhật # 2: Thư viện điều hướng này không hoạt động tốt với React với các Thẻ Html ban đầu của nó, vì vậy tôi phải đặt các tùy chọn và đổi tên các thẻ để sử dụng aria- * để nó không ảnh hưởng đến React.

navigation.setOption('prefix','aria-nv-el');
navigation.setOption('attrScope','aria-nv-scope');
navigation.setOption('attrScopeFOV','aria-nv-scope-fov');
navigation.setOption('attrScopeCurrent','aria-nv-scope-current');
navigation.setOption('attrElement','aria-nv-el');
navigation.setOption('attrElementFOV','aria-nv-el-fov');
navigation.setOption('attrElementCurrent','aria-nv-el-current');

@ Về cơ bản, tôi đang sử dụng ví dụ từ tệp này ( github.com/ahiipsa/navigation/blob/master/demo/index.html )
Thiago

Bạn không cần phải ràng buộc trước trong hàm tạo ( this.handleNVEnter = this.handleNVEnter.bind(this)) và sử dụng bộ khởi tạo thuộc tính ES7 với các hàm mũi tên ( handleNVEnter = enter => {}) vì các hàm mũi tên béo luôn bị ràng buộc. Nếu bạn có thể sử dụng cú pháp ES7, chỉ cần đi với điều đó.
Aaron Beall

1
Cảm ơn Aaron. Tôi đã có thể khắc phục sự cố. Tôi sẽ chấp nhận câu trả lời của bạn vì bây giờ tôi đang sử dụng giải pháp của bạn nhưng tôi cũng phải làm một việc khác. Vì các thẻ HTML của thư viện Nagivation không hoạt động tốt với React, nên tôi phải đặt tên thẻ trong cấu hình lib để sử dụng tiền tố aria- *, vấn đề là các sự kiện cũng được kích hoạt bằng cách sử dụng cùng một tiền tố, vì vậy hãy đặt sự kiện thành aria -nv-enter đã lừa! Bây giờ nó hoạt động tốt. Cảm ơn bạn!
Thiago

Tôi khuyên bạn nên thay đổi aria-*thành data-*vì các thuộc tính ARIA là từ một bộ tiêu chuẩn, bạn không thể tự tạo ra. Các thuộc tính dữ liệu có thể được đặt tùy ý hơn thành bất kỳ thứ gì bạn muốn.
Marcy Sutton

Câu trả lời:


86

Nếu bạn cần xử lý các sự kiện DOM chưa được React cung cấp, bạn phải thêm trình nghe DOM sau khi thành phần được gắn kết:

Cập nhật: Giữa React 13, 14 và 15, các thay đổi đã được thực hiện đối với API ảnh hưởng đến câu trả lời của tôi. Dưới đây là cách sử dụng React 15 và ES7 mới nhất. Xem lịch sử câu trả lời cho các phiên bản cũ hơn.

class MovieItem extends React.Component {

  componentDidMount() {
    // When the component is mounted, add your DOM listener to the "nv" elem.
    // (The "nv" elem is assigned in the render function.)
    this.nv.addEventListener("nv-enter", this.handleNvEnter);
  }

  componentWillUnmount() {
    // Make sure to remove the DOM listener when the component is unmounted.
    this.nv.removeEventListener("nv-enter", this.handleNvEnter);
  }

  // Use a class arrow function (ES7) for the handler. In ES6 you could bind()
  // a handler in the constructor.
  handleNvEnter = (event) => {
    console.log("Nv Enter:", event);
  }

  render() {
    // Here we render a single <div> and toggle the "aria-nv-el-current" attribute
    // using the attribute spread operator. This way only a single <div>
    // is ever mounted and we don't have to worry about adding/removing
    // a DOM listener every time the current index changes. The attrs 
    // are "spread" onto the <div> in the render function: {...attrs}
    const attrs = this.props.index === 0 ? {"aria-nv-el-current": true} : {};

    // Finally, render the div using a "ref" callback which assigns the mounted 
    // elem to a class property "nv" used to add the DOM listener to.
    return (
      <div ref={elem => this.nv = elem} aria-nv-el {...attrs} className="menu_item nv-default">
        ...
      </div>
    );
  }

}

Ví dụ trên Codepen.io


2
Bạn đang sử dụng sai findDOMNode. Trong trường hợp của bạn var elem = this.refs.nv;là đủ.
Pavlo

1
@Pavlo Hm, bạn nói đúng, có vẻ như điều này đã thay đổi trong v.14 (trả về phần tử DOM thay vì phần tử React như trong v.13, đó là những gì tôi đang sử dụng). Cảm ơn.
Aaron Beall

2
Tại sao tôi cần phải "Đảm bảo loại bỏ trình nghe DOM khi thành phần được ngắt kết nối"? Có nguồn nào mà họ sẽ tạo ra rò rỉ không?
Fen1kz

1
@levininja Nhấp vào edited Aug 19 at 6:19văn bản dưới bài đăng, sẽ đưa bạn đến lịch sử sửa đổi .
Aaron Beall

1
@ NicolasS.Xu React không cung cấp bất kỳ API điều phối sự kiện tùy chỉnh nào như bạn dự kiến ​​sẽ sử dụng các đạo cụ gọi lại (xem câu trả lời này ), nhưng bạn có thể sử dụng DOM tiêu chuẩn nv.dispatchEvent()nếu cần.
Aaron Beall

20

Bạn có thể sử dụng componentDidMountcomponentWillUnmount phương pháp:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class MovieItem extends Component
{
    _handleNVEvent = event => {
        ...
    };

    componentDidMount() {
        ReactDOM.findDOMNode(this).addEventListener('nv-event', this._handleNVEvent);
    }

    componentWillUnmount() {
        ReactDOM.findDOMNode(this).removeEventListener('nv-event', this._handleNVEvent);
    }

    [...]

}

export default MovieItem;

Xin chào @vbarbarosh, tôi đã cập nhật câu hỏi với nhiều thông tin chi tiết hơn
Thiago

4

Trước hết, các sự kiện tùy chỉnh không hoạt động tốt với các thành phần React nguyên bản. Vì vậy, bạn không thể chỉ nói <div onMyCustomEvent={something}>trong chức năng kết xuất, và phải suy nghĩ về vấn đề.

Thứ hai, sau khi xem qua tài liệu cho thư viện bạn đang sử dụng, sự kiện thực sự được kích hoạt document.body, vì vậy ngay cả khi nó hoạt động, trình xử lý sự kiện của bạn sẽ không bao giờ kích hoạt.

Thay vào đó, bên trong componentDidMountmột nơi nào đó trong ứng dụng của bạn, bạn có thể nghe nv-enter bằng cách thêm

document.body.addEventListener('nv-enter', function (event) {
    // logic
});

Sau đó, bên trong hàm gọi lại, hãy nhấn vào một hàm thay đổi trạng thái của thành phần hoặc bất cứ điều gì bạn muốn làm.


2
Bạn có thể giúp giải thích lý do tại sao "các sự kiện tùy chỉnh không hoạt động tốt với các thành phần React nguyên bản"?
codeful.element

1
@ codeful.element, trang web này có một số thông tin về điều đó: custom-elements-everywhere.com/#react
Paul-Hebert
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.