ReactJS - Dodaj niestandardowy detektor zdarzeń do komponentu

89

W zwykłym starym javascript mam DIV

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

i poniższy kod javascript

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

Teraz mam komponent React, wewnątrz tego komponentu, w metodzie render, zwracam mój div. Jak mogę dodać detektor zdarzeń do mojego wydarzenia niestandardowego? (Używam tej biblioteki do aplikacji telewizyjnych - nawigacja )

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;

Aktualizacja nr 1:

wprowadź opis obrazu tutaj

Zastosowałem wszystkie pomysły podane w odpowiedziach. Ustawiłem bibliotekę nawigacji na tryb debugowania i jestem w stanie poruszać się po elementach menu tylko w oparciu o klawiaturę (jak widać na zrzucie ekranu, mogłem przejść do Filmów 4), ale kiedy ustawiam 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 skomentowanych, ponieważ w obu przypadkach nie jestem w stanie uzyskać logowania linii konsoli.

Aktualizacja # 2: Ta biblioteka nawigacyjna nie działa dobrze z Reactem z oryginalnymi tagami HTML, więc musiałem ustawić opcje i zmienić nazwy tagów, aby używały aria- *, aby nie wpłynęło to na 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
źródło
@The Zasadniczo używam przykładu z tego pliku ( github.com/ahiipsa/navigation/blob/master/demo/index.html )
Thiago
Nie ma potrzeby wstępnego wiązania w konstruktorze ( this.handleNVEnter = this.handleNVEnter.bind(this)) i używania inicjatorów właściwości ES7 z funkcjami strzałkowymi ( handleNVEnter = enter => {}), ponieważ grube funkcje strzałkowe są zawsze powiązane. Jeśli możesz użyć składni ES7, po prostu idź z tym.
Aaron Beall
1
Dziękuję Aaron. Udało mi się rozwiązać problem. Przyjmę twoją odpowiedź, ponieważ teraz używam twojego rozwiązania, ale musiałem też zrobić coś innego. Ponieważ tagi HTML biblioteki Nagivation nie działają dobrze z Reactem, musiałem ustawić nazwy tagów w konfiguracji lib, aby używały prefiksu aria- *, problem polega na tym, że zdarzenia były również wyzwalane przy użyciu tego samego prefiksu, więc ustawiłem zdarzenie na arię -nv-enter załatwił sprawę! Teraz działa dobrze. Dziękuję Ci!
Thiago
Zalecałbym zmianę aria-*na, data-*ponieważ atrybuty ARIA pochodzą ze standardowego zestawu, nie możesz stworzyć własnego. Atrybuty danych można dowolnie ustawiać na dowolne.
Marcy Sutton,

Odpowiedzi:

88

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

Aktualizacja: Pomiędzy React 13, 14 i 15 wprowadzono zmiany w API, które wpływają na moją odpowiedź. Poniżej znajduje się najnowszy sposób korzystania z React 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

Aaron Beall
źródło
2
Nadużywasz findDOMNode. W twoim przypadku var elem = this.refs.nv;wystarczy.
Pavlo
1
@Pavlo Hm, masz rację, wygląda na to, że zmieniło się to w wersji 14 (aby zwrócić element DOM zamiast elementu React, jak w wersji 13, której używam). Dzięki.
Aaron Beall
2
Dlaczego muszę „Upewnij się, że usuwasz odbiornik DOM, gdy komponent jest odmontowany”? Czy jest jakieś źródło, które spowoduje wyciek?
Fen1kz
1
@levininja Kliknij edited Aug 19 at 6:19tekst pod postem, który przeniesie Cię do historii zmian .
Aaron Beall
1
@ NicolasS.Xu React nie zapewnia żadnego niestandardowego interfejsu API do wysyłania zdarzeń, ponieważ oczekuje się , że będziesz używać właściwości wywołania zwrotnego (zobacz tę odpowiedź ), ale możesz użyć standardowego DOM, nv.dispatchEvent()jeśli chcesz.
Aaron Beall
20

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;
vbarbarosh
źródło
Cześć @vbarbarosh, zaktualizowałem pytanie o więcej szczegółów
Thiago
4

Po pierwsze, zdarzenia niestandardowe nie współgrają z komponentami React 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 document.bodyużywanej biblioteki zdarzenie jest faktycznie uruchamiane , więc nawet gdyby zadziałało, program obsługi zdarzeń nigdy by się nie wyzwolił.

Zamiast tego, componentDidMountgdzieś w aplikacji, możesz posłuchać nv-enter, dodając

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

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

dannyjolie
źródło
2
Czy mógłbyś pomóc wyjaśnić powód, dla którego „zdarzenia niestandardowe nie działają dobrze z natywnymi komponentami React”?
codeful.element
1
@ codeful.element, ta strona zawiera pewne informacje na ten temat: custom-elements-everywhere.com/#react
Paul-Hebert