ReactJS - Aggiungi un listener di eventi personalizzato al componente


89

Nel semplice vecchio javascript ho il DIV

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

e il seguente codice javascript

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

Ora ho un componente React, all'interno di questo componente, nel metodo di rendering, sto restituendo il mio div. Come posso aggiungere un listener di eventi per il mio evento personalizzato? (Sto usando questa libreria per le app TV - navigazione )

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;

Aggiornamento n. 1:

inserisci qui la descrizione dell'immagine

Ho applicato tutte le idee fornite nelle risposte. Ho impostato la libreria di navigazione in modalità debug e sono in grado di navigare sulle mie voci di menu solo in base alla tastiera (come puoi vedere nello screenshot sono stato in grado di navigare su Movies 4) ma quando metto a fuoco un elemento nel menu o premi invio, non vedo nulla nella console.

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;

Ho lasciato alcune righe commentate perché in entrambi i casi non riesco a registrare le righe della console.

Aggiornamento n. 2: questa libreria di navigazione non funziona bene con React con i suoi tag HTML originali, quindi ho dovuto impostare le opzioni e rinominare i tag per usare aria- * in modo che non influisse su 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');

@The Sto fondamentalmente usando l'esempio da questo file ( github.com/ahiipsa/navigation/blob/master/demo/index.html )
Thiago

Non è necessario pre-legare nel costruttore ( this.handleNVEnter = this.handleNVEnter.bind(this)) e utilizzare gli inizializzatori di proprietà ES7 con le funzioni freccia ( handleNVEnter = enter => {}) perché le funzioni freccia grossa sono sempre associate. Se puoi usare la sintassi ES7, segui quella.
Aaron Beall

1
Grazie Aaron. Sono riuscito a risolvere il problema. Accetterò la tua risposta poiché ora sto usando la tua soluzione, ma dovevo anche fare qualcos'altro. Poiché i tag HTML della libreria Nagivation non funzionano bene con React, ho dovuto impostare i nomi dei tag nella configurazione lib per utilizzare il prefisso aria- *, il problema è che anche gli eventi sono stati attivati ​​utilizzando lo stesso prefisso, quindi impostare l'evento su aria -nv-enter ha fatto il trucco! Funziona bene ora. Grazie!
Thiago

Consiglierei di passare aria-*a data-*perché gli attributi ARIA provengono da un set standard, non puoi crearne uno tuo. Gli attributi dei dati possono essere impostati in modo più arbitrario su ciò che desideri.
Marcy Sutton

Risposte:


88

Se devi gestire eventi DOM non già forniti da React, devi aggiungere listener DOM dopo che il componente è stato montato:

Aggiornamento: tra React 13, 14 e 15 sono state apportate modifiche all'API che hanno effetto sulla mia risposta. Di seguito è riportato l'ultimo modo di utilizzare React 15 ed ES7. Visualizza la cronologia delle risposte per le versioni precedenti.

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

}

Esempio su Codepen.io


2
Stai abusando findDOMNode. Nel tuo caso var elem = this.refs.nv;è sufficiente.
Pavlo

1
@Pavlo Hm, hai ragione, sembra che questo sia cambiato nella v.14 (per restituire l'elemento DOM invece dell'elemento React come nella v.13, che è quello che sto usando). Grazie.
Aaron Beall

2
Perché devo "assicurarmi di rimuovere il listener DOM quando il componente è smontato"? C'è qualche fonte che creeranno una perdita?
Fen1kz

1
@levininja Fai clic sul edited Aug 19 at 6:19testo sotto il post, che ti porta alla cronologia delle revisioni .
Aaron Beall

1
@ NicolasS.Xu React non fornisce alcuna API personalizzata per l'invio di eventi poiché ci si aspetta che tu usi i callback props (vedi questa risposta ), ma puoi usare DOM standard nv.dispatchEvent()se necessario.
Aaron Beall

20

È possibile utilizzare i metodi componentDidMount e componentWillUnmount :

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;

Ciao @vbarbarosh, ho aggiornato la domanda con maggiori dettagli
Thiago

4

Prima di tutto, gli eventi personalizzati non funzionano bene con i componenti React in modo nativo. Quindi non puoi semplicemente dire <div onMyCustomEvent={something}>nella funzione di rendering e devi pensare al problema.

In secondo luogo, dopo aver dato un'occhiata alla documentazione per la libreria che stai utilizzando, l'evento viene effettivamente document.bodyattivato, quindi anche se funzionasse, il tuo gestore di eventi non si attiverebbe mai.

Invece, componentDidMountda qualche parte nella tua applicazione, puoi ascoltare nv-enter aggiungendo

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

Quindi, all'interno della funzione di callback, premi una funzione che cambia lo stato del componente o qualunque cosa tu voglia fare.


2
Potreste aiutarci a spiegare il motivo per cui "gli eventi personalizzati non funzionano bene con i componenti React in modo nativo"?
codeful.element

1
@ codeful.element, questo sito ha alcune informazioni su questo: custom-elements-everywhere.com/#react
Paul-Hebert
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.