【问题标题】:ReactJS - Add custom event listener to componentReactJS - 向组件添加自定义事件监听器
【发布时间】:2016-03-23 14:03:54
【问题描述】:

在普通的旧 HTML 中,我有 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。如何为我的自定义事件添加事件侦听器? (我正在将此库用于电视应用程序 - navigation

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:这个导航库不适用于带有原始 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');

【问题讨论】:

  • @我基本上是在使用这个文件中的例子(github.com/ahiipsa/navigation/blob/master/demo/index.html
  • 你不需要在你的构造函数中预先绑定 (this.handleNVEnter = this.handleNVEnter.bind(this)) 和使用带有箭头函数的 ES7 属性初始化器 (handleNVEnter = enter =&gt; {}) 因为胖箭头函数总是被绑定的。如果你可以使用 ES7 语法,那就去吧。
  • 谢谢亚伦。我能够解决问题。我将接受您的回答,因为我现在正在使用您的解决方案,但我还必须做其他事情。由于 Nagivation 库 HTML 标签不能很好地与 React 配合使用,我不得不在 lib 配置中设置标签名称以使用 aria-* 前缀,问题是事件也使用相同的前缀触发,因此将事件设置为 aria -nv-enter 成功了!它现在工作正常。谢谢!
  • 我建议将 aria-* 更改为 data-*,因为 ARIA 属性来自标准集,您无法自己制作。数据属性可以随意设置。

标签: reactjs


【解决方案1】:

如果你需要handle DOM events not already provided by React你必须在组件挂载后添加DOM监听器:

更新:在 React 13、14 和 15 之间对影响我的回答的 API 进行了更改。下面是使用 React 15 和 ES7 的最新方法。旧版本请见answer history

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

}

Example on Codepen.io

【讨论】:

  • 您在滥用findDOMNode。在你的情况下var elem = this.refs.nv; 就足够了。
  • @Pavlo 嗯,你是对的,这似乎在 v.14 中发生了变化(返回 DOM 元素而不是 v.13 中的 React 元素,这是我正在使用的) .谢谢。
  • 为什么我需要“确保在卸载组件时删除 DOM 侦听器”?是否有任何来源会造成泄漏?
  • @levininja 点击帖子下方的edited Aug 19 at 6:19 文字,跳转至revision history
  • @NicolasS.Xu React 不提供任何自定义事件调度 API,因为您应该使用回调道具(请参阅 this answer),但如果需要,您可以使用标准 DOM nv.dispatchEvent() .
【解决方案2】:

您可以使用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,我用更多细节更新了这个问题
【解决方案3】:

首先,自定义事件不能很好地与原生的 React 组件配合使用。所以你不能只在渲染函数中说&lt;div onMyCustomEvent={something}&gt;,而必须考虑问题。

其次,在查看了您正在使用的库的文档后,该事件实际上是在 document.body 上触发的,因此即使它确实有效,您的事件处理程序也永远不会触发。

相反,在您的应用程序某处的 componentDidMount 中,您可以通过添加来收听 nv-enter

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

然后,在回调函数中,点击一个改变组件状态的函数,或者你想做的任何事情。

【讨论】:

  • 您能否帮助详细说明“自定义事件不能很好地与 React 组件原生配合”的原因?
  • @codeful.element,这个网站有一些信息:custom-elements-everywhere.com/#react
【解决方案4】:

我建议使用React.createRef()ref=this.elementRef 而不是ReactDOM.findDOMNode(this) 来获取DOM 元素引用。通过这种方式,您可以获得对 DOM 元素的引用作为实例变量。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class MenuItem extends Component {

  constructor(props) {
    super(props);

    this.elementRef = React.createRef();
  }

  handleNVFocus = event => {
      console.log('Focused: ' + this.props.menuItem.caption.toUpperCase());
  }
    
  componentDidMount() {
    this.elementRef.addEventListener('nv-focus', this.handleNVFocus);
  }

  componentWillUnmount() {
    this.elementRef.removeEventListener('nv-focus', this.handleNVFocus);
  }

  render() {
    return (
      <element ref={this.elementRef} />
    )
  }

}

export default MenuItem;

【讨论】:

    【解决方案5】:

    这是一个dannyjolie 更详细的答案,不需要组件参考,而是使用document.body 参考。

    首先在您的应用程序中,有一个组件方法将创建一个新的自定义事件并发送它。 例如,您的客户切换语言。 在这种情况下,您可以在文档正文中附加一个新事件:

    setLang(newLang) {
        // lang business logic here
        // then throw a new custom event attached to the body :
        document.body.dispatchEvent(new CustomEvent("my-set-lang", {detail: { newLang }}));
      }
    

    完成后,您将拥有另一个需要监听 lang 切换事件的组件。例如,您的客户正在使用给定产品,您将使用新语言作为参数刷新产品。

    首先为您的目标组件添加/删除事件监听器:

      componentDidMount() {
        document.body.addEventListener('my-set-lang', this.handleLangChange.bind(this));
      }
    
      componentWillUnmount() {
        document.body.removeEventListener('my-set-lang', this.handleLangChange.bind(this));
      }
    
    

    然后定义你的组件my-set-langwhandler

      handleLangChange(event) {
        console.log("lang has changed to", event.detail.newLang);
        // your business logic here .. this.setState({...});
      }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-02-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-13
      相关资源
      最近更新 更多