ReactJS - Benutzerdefinierten Ereignis-Listener zur Komponente hinzufügen

87

In einfachem altem Javascript habe ich den DIV

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

und den folgenden Javascript-Code

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

Jetzt habe ich eine React-Komponente in dieser Komponente, in der Render-Methode gebe ich mein div zurück. Wie kann ich einen Ereignis-Listener für mein benutzerdefiniertes Ereignis hinzufügen? (Ich benutze diese Bibliothek für TV-Apps - Navigation )

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;

Update Nr. 1:

Geben Sie hier die Bildbeschreibung ein

Ich habe alle in den Antworten enthaltenen Ideen angewendet. Ich habe die Navigationsbibliothek auf den Debug-Modus eingestellt und kann auf meinen Menüelementen nur über die Tastatur navigieren (wie Sie im Screenshot sehen können, konnte ich zu Filmen 4 navigieren), aber wenn ich ein Element im Menü oder fokussiere Drücken Sie die Eingabetaste. Ich sehe nichts in der Konsole.

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;

Ich habe einige Zeilen kommentiert, da ich in beiden Fällen nicht in der Lage bin, die zu protokollierenden Konsolenzeilen abzurufen.

Update Nr. 2: Diese Navigationsbibliothek funktioniert nicht gut mit React mit den ursprünglichen HTML-Tags. Daher musste ich die Optionen festlegen und die Tags umbenennen, um aria- * zu verwenden, damit React nicht beeinträchtigt wird.

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
quelle
@Das ich benutze im Grunde das Beispiel aus dieser Datei ( github.com/ahiipsa/navigation/blob/master/demo/index.html )
Thiago
Sie müssen nicht sowohl in Ihrem Konstruktor ( this.handleNVEnter = this.handleNVEnter.bind(this)) vorbinden als auch ES7-Eigenschaftsinitialisierer mit Pfeilfunktionen ( ) verwenden, da Fettpfeilfunktionen handleNVEnter = enter => {}immer gebunden sind. Wenn Sie die ES7-Syntax verwenden können, gehen Sie einfach damit um.
Aaron Beall
1
Danke Aaron. Ich konnte das Problem beheben. Ich werde Ihre Antwort akzeptieren, da ich jetzt Ihre Lösung verwende, aber ich musste auch noch etwas anderes tun. Da die HTML-Tags der Nagivation-Bibliothek mit React nicht gut funktionieren, musste ich die Tag-Namen in der lib-Konfiguration festlegen, um das Präfix aria- * zu verwenden. Das Problem besteht darin, dass die Ereignisse auch mit demselben Präfix ausgelöst wurden, sodass das Ereignis auf aria gesetzt wurde -nv-enter hat es geschafft! Es funktioniert jetzt gut. Vielen Dank!
Thiago
Ich würde empfehlen, zu zu wechseln aria-*, data-*da ARIA-Attribute aus einem Standardsatz stammen und Sie keine eigenen erstellen können. Datenattribute können beliebiger eingestellt werden.
Marcy Sutton

Antworten:

85

Wenn Sie DOM-Ereignisse verarbeiten müssen, die nicht bereits von React bereitgestellt wurden , müssen Sie nach dem Mounten der Komponente DOM-Listener hinzufügen:

Update: Zwischen Reaktion 13, 14 und 15 wurden Änderungen an der API vorgenommen, die sich auf meine Antwort auswirken. Im Folgenden finden Sie die neueste Methode zur Verwendung von React 15 und ES7. Siehe Antwortverlauf für ältere Versionen.

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

}

Beispiel auf Codepen.io

Aaron Beall
quelle
2
Sie missbrauchen findDOMNode. In deinem Fall var elem = this.refs.nv;ist genug.
Pavlo
1
@Pavlo Hm, Sie haben Recht, es scheint, dass sich dies in Version 14 geändert hat (um das DOM-Element anstelle des React-Elements wie in Version 13 zurückzugeben, was ich verwende). Vielen Dank.
Aaron Beall
2
Warum muss ich "sicherstellen, dass der DOM-Listener entfernt wird, wenn die Komponente nicht gemountet ist"? Gibt es eine Quelle, aus der sie ein Leck verursachen?
Fen1kz
1
@levininja Klicken Sie auf den edited Aug 19 at 6:19Text unter dem Beitrag, um zum Revisionsverlauf zu gelangen .
Aaron Beall
1
@ NicolasS.Xu React bietet keine benutzerdefinierte API für die Ereignisverteilung, da von Ihnen erwartet wird, dass Sie Rückruf-Requisiten verwenden (siehe diese Antwort ). Sie können jedoch bei Bedarf Standard-DOM verwenden nv.dispatchEvent().
Aaron Beall
20

Sie können die Methoden componentDidMount und componentWillUnmount verwenden:

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
quelle
Hallo @vbarbarosh, ich habe die Frage mit weiteren Details aktualisiert
Thiago
4

Zunächst einmal spielen benutzerdefinierte Ereignisse mit nativen React-Komponenten nicht gut. Sie können also nicht einfach <div onMyCustomEvent={something}>in der Renderfunktion sagen und müssen über das Problem nachdenken.

Zweitens wird das Ereignis ausgelöst, nachdem Sie einen Blick auf die Dokumentation der von Ihnen verwendeten Bibliothek geworfen haben. document.bodySelbst wenn es funktioniert hätte, würde Ihr Ereignishandler niemals ausgelöst.

Stattdessen können Sie componentDidMountirgendwo in Ihrer Anwendung nv-enter durch Hinzufügen anhören

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

Drücken Sie dann in der Rückruffunktion eine Funktion, die den Status der Komponente oder was auch immer Sie tun möchten, ändert.

dannyjolie
quelle
2
Können Sie den Grund erläutern, warum "benutzerdefinierte Ereignisse mit nativen React-Komponenten nicht gut funktionieren"?
codeful.element
1
@ codeful.element, diese Seite enthält einige Informationen dazu: custom-elements-everywhere.com/#react
Paul-Hebert