ReactJS - Добавить пользовательский прослушиватель событий в компонент

87

В простом старом javascript у меня есть DIV

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

и следующий код javascript

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

Теперь у меня есть компонент React, внутри этого компонента, в методе рендеринга, я возвращаю свой div. Как я могу добавить прослушиватель событий для моего настраиваемого события? (Я использую эту библиотеку для ТВ приложений - навигация )

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;

Обновление №1:

введите описание изображения здесь

Я применил все идеи, изложенные в ответах. Я установил библиотеку навигации в режим отладки, и я могу перемещаться по элементам меню только с помощью клавиатуры (как вы можете видеть на снимке экрана, я смог перейти к Фильмам 4), но когда я фокусирую элемент в меню или жму ввод, в консоли ничего не вижу.

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;

Я оставил некоторые строки прокомментированными, потому что в обоих случаях я не могу зарегистрировать строки консоли.

Обновление №2: эта навигационная библиотека плохо работает с React с его исходными тегами Html, поэтому мне пришлось установить параметры и переименовать теги, чтобы использовать aria- *, чтобы это не повлияло на 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');
Тьяго
источник
@ Я в основном использую пример из этого файла ( github.com/ahiipsa/navigation/blob/master/demo/index.html )
Thiago
Вам не нужно одновременно выполнять предварительную привязку в вашем конструкторе ( this.handleNVEnter = this.handleNVEnter.bind(this)) и использовать инициализаторы свойств ES7 с функциями стрелок ( handleNVEnter = enter => {}), потому что функции жирных стрелок всегда связаны. Если вы можете использовать синтаксис ES7, просто сделайте это.
Аарон Билл,
1
Спасибо, Аарон. Я смог решить проблему. Я собираюсь принять ваш ответ, поскольку сейчас использую ваше решение, но мне также пришлось сделать кое-что еще. Поскольку HTML-теги библиотеки Nagivation плохо сочетаются с React, мне пришлось установить имена тегов в конфигурации lib для использования префикса aria- *, проблема в том, что события также запускались с использованием того же префикса, поэтому установите для события aria -nv-enter сделали свое дело! Теперь он работает нормально. Спасибо!
Thiago
Я бы порекомендовал перейти aria-*на, data-*потому что атрибуты ARIA взяты из стандартного набора, вы не можете создавать свои собственные. Атрибуты данных могут быть более произвольно установлены так, как вы хотите.
Марси Саттон

Ответы:

86

Если вам нужно обрабатывать события DOM, которые еще не предоставлены React, вам необходимо добавить прослушиватели DOM после монтирования компонента:

Обновление: между React 13, 14 и 15 в API были внесены изменения, которые повлияли на мой ответ. Ниже представлен последний способ использования React 15 и ES7. См. Историю ответов для более старых версий.

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

}

Пример на Codepen.io

Аарон Билл
источник
2
Вы неправильно используете findDOMNode. В твоем случае этого var elem = this.refs.nv;достаточно.
Павло
1
@Pavlo Хм, вы правы, похоже, что это изменилось в версии 14 (чтобы вернуть элемент DOM вместо элемента React, как в версии 13, что я использую). Спасибо.
Aaron Beall
2
Зачем мне нужно «Обязательно удалять прослушиватель DOM, когда компонент размонтирован»? Есть ли источник утечки?
Fen1kz
1
@levininja Щелкните edited Aug 19 at 6:19текст под сообщением, чтобы перейти к истории изменений .
Aaron Beall
1
@ NicolasS.Xu React не предоставляет никакого настраиваемого API диспетчеризации событий, так как ожидается, что вы будете использовать реквизиты обратного вызова (см. Этот ответ ), но вы можете использовать стандартную DOM, nv.dispatchEvent()если вам нужно.
Аарон Билл
20

Вы можете использовать методы componentDidMount и 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, я обновил вопрос более подробной информацией
Тьяго
4

Во-первых, пользовательские события изначально не работают с компонентами React. Таким образом, вы не можете просто сказать <div onMyCustomEvent={something}>в функции рендеринга и должны обдумать проблему.

Во-вторых, после просмотра документации по используемой вами библиотеке событие фактически запускается document.body, поэтому, даже если оно сработает, ваш обработчик событий никогда не сработает.

Вместо этого внутри componentDidMountгде-то в вашем приложении вы можете прослушивать nv-enter, добавив

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

Затем внутри функции обратного вызова нажмите функцию, которая изменяет состояние компонента, или все, что вы хотите сделать.

данниджоли
источник
2
Не могли бы вы объяснить причину, по которой «настраиваемые события не работают с компонентами React изначально»?
codeful.element
1
@ codeful.element, на этом сайте есть информация по этому поводу
Пол-Хеберт,