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.