ReactJS - Tambahkan pendengar acara khusus ke komponen

89

Dalam javascript lama biasa saya memiliki DIV

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

dan kode javascript berikut

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

Sekarang saya memiliki Komponen React, di dalam komponen ini, dalam metode render, saya mengembalikan div saya. Bagaimana cara menambahkan event listener untuk acara khusus saya? (Saya menggunakan perpustakaan ini untuk aplikasi TV - navigasi )

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;

Perbarui # 1:

masukkan deskripsi gambar di sini

Saya menerapkan semua ide yang diberikan dalam jawaban. Saya mengatur perpustakaan navigasi ke mode debug dan saya dapat menavigasi item menu saya hanya berdasarkan keyboard (seperti yang Anda lihat di tangkapan layar, saya dapat menavigasi ke Film 4) tetapi ketika saya memfokuskan item di menu atau tekan enter, saya tidak melihat apa pun di konsol.

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;

Saya meninggalkan beberapa baris komentar karena dalam kedua kasus tersebut saya tidak bisa mendapatkan baris konsol untuk dicatat.

Pembaruan # 2: Pustaka navigasi ini tidak bekerja dengan baik dengan React dengan Tag Html aslinya, jadi saya harus mengatur opsi dan mengganti nama tag untuk menggunakan aria- * sehingga tidak akan mempengaruhi 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');
Thiago
sumber
@ Saya pada dasarnya menggunakan contoh dari file ini ( github.com/ahiipsa/navigation/blob/master/demo/index.html )
Thiago
Anda tidak perlu mengikat terlebih dahulu di konstruktor ( this.handleNVEnter = this.handleNVEnter.bind(this)) dan menggunakan penginisialisasi properti ES7 dengan fungsi panah ( handleNVEnter = enter => {}) karena fungsi panah lemak selalu terikat. Jika Anda dapat menggunakan sintaks ES7, lakukan saja.
Aaron Beall
1
Terima kasih Aaron. Saya bisa memperbaiki masalah. Saya akan menerima jawaban Anda karena saya sekarang menggunakan solusi Anda tetapi saya juga harus melakukan sesuatu yang lain. Karena tag HTML pustaka Nagivation tidak berfungsi dengan baik dengan React, saya harus menyetel nama tag di konfigurasi lib untuk menggunakan awalan aria- *, masalahnya adalah bahwa kejadian juga dipicu menggunakan awalan yang sama, jadi menyetel acara ke aria -nv-enter berhasil! Ini bekerja dengan baik sekarang. Terima kasih!
Thiago
Saya akan merekomendasikan mengubah aria-*ke data-*karena atribut ARIA berasal dari kumpulan standar, Anda tidak dapat membuatnya sendiri. Atribut data dapat diatur secara lebih sewenang-wenang ke apa pun yang Anda inginkan.
Marcy Sutton

Jawaban:

88

Jika Anda perlu menangani kejadian DOM yang belum disediakan oleh React, Anda harus menambahkan pendengar DOM setelah komponen dipasang:

Pembaruan: Antara React 13, 14, dan 15 perubahan dibuat pada API yang mempengaruhi jawaban saya. Di bawah ini adalah cara terbaru menggunakan React 15 dan ES7. Lihat riwayat jawaban untuk versi yang lebih lama.

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

}

Contoh di Codepen.io

Aaron Beall
sumber
2
Anda menyalahgunakan findDOMNode. Dalam kasus Anda var elem = this.refs.nv;sudah cukup.
Pavlo
1
@Pavlo Hm, Anda benar, tampaknya ini berubah di v.14 (untuk mengembalikan elemen DOM alih-alih elemen React seperti di v.13, yang saya gunakan). Terima kasih.
Aaron Beall
2
Mengapa saya perlu "Pastikan untuk menghapus listener DOM saat komponen dilepas"? Apakah ada sumber bahwa mereka akan membuat kebocoran?
Fen1kz
1
@levininja Klik edited Aug 19 at 6:19teks di bawah posting, yang membawa Anda ke riwayat revisi .
Aaron Beall
1
@ NicolasS.Xu React tidak menyediakan API pengiriman peristiwa khusus karena Anda diharapkan menggunakan alat peraga panggilan balik (lihat jawaban ini ), tetapi Anda dapat menggunakan DOM standar nv.dispatchEvent()jika perlu.
Aaron Beall
20

Anda bisa menggunakan componentDidMount dan componentWillUnmount metode:

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;
vbarbarosh
sumber
Hai @vbarbarosh, saya memperbarui pertanyaan dengan detail lebih lanjut
Thiago
4

Pertama, acara khusus tidak berfungsi dengan baik dengan komponen React secara native. Jadi Anda tidak bisa hanya mengatakan <div onMyCustomEvent={something}>dalam fungsi render, dan harus memikirkan masalahnya.

Kedua, setelah mengintip dokumentasi untuk pustaka yang Anda gunakan, peristiwa sebenarnya diaktifkan document.body, jadi meskipun berhasil, penangan peristiwa Anda tidak akan pernah terpicu.

Sebagai gantinya, di dalam componentDidMountsuatu tempat di aplikasi Anda, Anda dapat mendengarkan nv-enter dengan menambahkan

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

Kemudian, di dalam fungsi callback, tekan fungsi yang mengubah status komponen, atau apa pun yang ingin Anda lakukan.

dannyjolie
sumber
2
Bisakah Anda membantu menjelaskan alasan mengapa "peristiwa khusus tidak berjalan baik dengan komponen React secara native"?
codeful.element
1
@ codeful.element, situs ini memiliki beberapa info tentang itu: custom-elements-everywhere.com/#react
Paul-Hebert