ReactJS-Dodaj własny detektor zdarzeń do komponentu

W zwykłym starym javascript mam DIV

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

Oraz poniższy kod javascript

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

Teraz mam komponent Reactowy, wewnątrz tego komponentu, w metodzie render zwracam mój div. Jak Mogę dodać odbiornik zdarzeń do mojego wydarzenia niestandardowego? (Używam tej biblioteki dla aplikacji TV - https://github.com/ahiipsa/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 #1:

Tutaj wpisz opis obrazka

Zastosowałem wszystkie pomysły zawarte w odpowiedziach. Ustawiłem nawigację biblioteka do trybu debugowania i jestem w stanie poruszać się po moich elementach menu tylko na podstawie klawiatury (jak widać na zrzucie ekranu udało mi się przejść do filmów 4), ale kiedy skupiam element w menu lub naciśnij enter, nie widzę nic w konsoli.
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;

Zostawiłem kilka linii komentowanych, ponieważ w obu przypadkach nie jestem w stanie uzyskać linii konsoli do zalogowania.

Update #2: Ta biblioteka nawigacyjna nie działa dobrze z Reactem z oryginalnymi znacznikami Html, więc musiałem ustawić opcje i zmień nazwę znaczników na ARIA -*, aby nie wpływało to na Reacta.

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');
Author: soundslikeodd, 2016-03-23

3 answers

Jeśli chcesz obsłużyć zdarzenia DOM, które nie zostały jeszcze dostarczone przez Reacta , musisz dodać detektory dom po zamontowaniu komponentu:

Aktualizacja: pomiędzy Reactem 13, 14 i 15 wprowadzono zmiany w API, które wpłynęły na moją odpowiedź. Poniżej przedstawiamy najnowszy sposób korzystania z Reacta 15 i ES7. Zobacz historię odpowiedzi dla starszych wersji.

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

}

Przykład na Codepen.io

 38
Author: Aaron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-19 06:19:07

Możesz użyć metod componentDidMount i 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;
 14
Author: vbarbarosh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-23 14:54:32

Po pierwsze, niestandardowe zdarzenia nie grają dobrze z komponentami Reactowymi natywnie. Więc nie możesz po prostu powiedzieć <div onMyCustomEvent={something}> w funkcji renderowania i musisz przemyśleć problem.

Po Drugie, po przejrzeniu dokumentacji biblioteki, której używasz, zdarzenie jest uruchamiane document.body, więc nawet gdyby zadziałało, Twój program obsługi zdarzenia nigdy by się nie uruchomił.

Zamiast tego, wewnątrz componentDidMount gdzieś w Twojej aplikacji, możesz odsłuchać nv-enter dodając

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

Następnie, wewnątrz funkcja wywołania zwrotnego, naciśnij funkcję, która zmienia stan komponentu lub cokolwiek chcesz zrobić.

 1
Author: dannyjolie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-23 14:55:08