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 Component, всередині цього компонента, у методі render, я повертаю свій 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:

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

Я застосував усі ідеї, наведені у відповідях. Я встановлюю навігаційну бібліотеку в режим налагодження, і я можу переходити по пунктах меню лише на основі клавіатури (як ви можете бачити на знімку екрана, я зміг перейти до Movies 4), але коли я фокусую елемент у меню або натисніть Enter, я не бачу нічого на консолі.

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 )
Тіаго

Вам не потрібно як попередньо прив’язувати свій конструктор ( this.handleNVEnter = this.handleNVEnter.bind(this)), так і використовувати ініціалізатори властивостей ES7 з функціями стрілок ( handleNVEnter = enter => {}), оскільки функції жирної стрілки завжди пов’язані. Якщо ви можете використовувати синтаксис ES7, просто подумайте.
Аарон Білл

1
Дякую Аароне. Я зміг вирішити проблему. Я прийму вашу відповідь, оскільки зараз використовую ваше рішення, але мені також довелося зробити щось інше. Оскільки теги HTML бібліотеки Nagivation погано граються з React, мені довелося встановити імена тегів у конфігурації lib, щоб використовувати префікс aria- *, проблема полягає в тому, що події також запускалися за допомогою того самого префікса, тому встановлення події на aria -nv-enter зробив свою справу! Зараз він працює нормально. Дякую!
Тьяго

Я б рекомендував змінити 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, що я і використовую). Дякую.
Аарон Білл

2
Чому мені потрібно "Обов’язково видалити прослуховувач DOM, коли компонент демонтується"? Чи існує джерело, яке призведе до витоку інформації?
Fen1kz

1
@levininja Клацніть на edited Aug 19 at 6:19текст під дописом, який призведе до перегляду історії .
Аарон Білл

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, на цьому сайті є деяка інформація про це: custom-elements-everywhere.com/#react
Paul-Hebert
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.