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

现在我在render方法中的这个组件内部有一个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:

在此处输入图片说明

我应用了答案中提供的所有想法。我将导航库设置为调试模式,并且只能基于键盘在菜单项上进行导航(如您在屏幕快照中所见,我能够导航至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:这个导航库不能与带有原始Html标签的React一起很好地使用,所以我必须设置选项并重命名标签以使用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');

@The我基本上使用此文件(例子github.com/ahiipsa/navigation/blob/master/demo/index.html
蒂亚戈-

您不需要预先绑定在构造函数中(this.handleNVEnter = this.handleNVEnter.bind(this)),也不需要将ES7属性初始化器与箭头函数(handleNVEnter = enter => {})一起使用,因为始终会绑定胖箭头函数。如果可以使用ES7语法,那就去吧。
亚伦·比尔

1
谢谢亚伦。我能够解决问题。我将接受您的回答,因为我现在正在使用您的解决方案,但我还必须做其他事情。由于Nagivation库的HTML标签不能与React很好地配合使用,因此我不得不在lib配置中将标签名称设置为使用aria- *前缀,问题是事件也使用相同的前缀触发,因此将事件设置为aria -nv-enter完成了!现在工作正常。谢谢!
Thiago

我建议更改aria-*为,data-*因为ARIA属性来自标准集,因此您无法组成自己的属性。数据属性可以更任意地设置为所需的任何值。
Marcy Sutton

Answers:


86

如果您需要处理React尚未提供的DOM事件,则必须在组件安装后添加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 Hm,您是对的,看来这在v.14中已更改(返回DOM元素,而不是像我在v.13中那样返回v.13中的React元素)。谢谢。
亚伦·比尔

2
为什么需要“确保在卸载组件时删除DOM侦听器”?是否有任何来源会造成泄漏?
Fen1kz

1
@levininja单击edited Aug 19 at 6:19帖子下方的文本,以转到修订历史记录
亚伦·比尔

1
@ NicolasS.Xu React不提供任何自定义事件调度API,因为您应该使用回调道具(请参阅此答案),但是nv.dispatchEvent()如果需要,您可以使用标准DOM 。
亚伦·比尔

20

您可以使用componentDidMountcomponentWillUnmount方法:

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,您好,我更新了更多详细的问题
Thiago

4

首先,自定义事件不适用于本机的React组件。因此,您不能只<div onMyCustomEvent={something}>在render函数中说,而必须考虑问题。

其次,在查看了正在使用的库的文档之后,实际上会触发该事件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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.