ReactJS-カスタムイベントリスナーをコンポーネントに追加


89

プレーンな古い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を返します。カスタムイベントのイベントリスナーを追加するにはどうすればよいですか?(私はこのライブラリをTVアプリに使用しています-ナビゲーション

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にナビゲートできました)が、メニュー内の項目にフォーカスしたり、 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ではうまく機能しないため、Reactに影響を与えないように、オプションを設定し、タグの名前をaria- *を使用するように変更する必要がありました。

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))で事前バインドする必要はなく、矢印関数(handleNVEnter = enter => {})でES7プロパティ初期化子を使用する必要もありません。ES7構文を使用できる場合は、それを使用してください。
アーロンビール2016年

1
アーロンありがとう。私は問題を解決することができました。私は今あなたの解決策を使用しているのであなたの答えを受け入れるつもりですが、私はまた何か他のことをしなければなりませんでした。NagivationライブラリのHTMLタグはReactでうまく機能しないため、lib構成でタグ名を設定してaria- *プレフィックスを使用する必要がありました。問題は、イベントも同じプレフィックスを使用してトリガーされたため、イベントをariaに設定することです-nv-enterがトリックを行いました!現在は正常に動作しています。ありがとうございました!
チアゴ

ARIA属性は標準セットからのものであり、独自の属性を作成することはできないため、に変更aria-*するdata-*ことをお勧めします。データ属性は、より任意に任意に設定できます。
マーシーサットン

回答:


88

もしあなたが必要ならば Reactによってまだ提供されていないDOMイベント処理は、コンポーネントがマウントされた後にDOMリスナーを追加する必要があります。

更新: React 13、14、および15の間に、私の回答に影響するAPIに変更が加えられました。以下は、React15と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;は十分です。
パブロ2016年

1
@Pavloうーん、そうです、これはv.14で変更されたようです(私が使用しているv.13のようにReact要素の代わりにDOM要素を返すため)。ありがとう。
アーロンビール2016年

2
「コンポーネントがマウント解除されたときに必ずDOMリスナーを削除する」必要があるのはなぜですか?それらがリークを引き起こす原因はありますか?
fen1kz 2017

1
@levininjaedited Aug 19 at 6:19投稿の下のテキストをクリックすると、改訂履歴が表示されます。
アーロンビール2017

1
@ NicolasS.Xu Reactは、コールバック小道具を使用することが期待されているため、カスタムイベントディスパッチAPIを提供しません(この回答を参照)が、nv.dispatchEvent()必要に応じて標準DOMを使用できます。
アーロンビール2018年

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}>、レンダリング関数で言うことはできず、問題について考える必要があります。

次に、使用しているライブラリのドキュメントを確認した後、イベントが実際に発生します。 document.bodyに発生するため、機能したとしても、イベントハンドラーがトリガーされることはありません。

代わりに、componentDidMountアプリケーションのどこかに、追加することでnv-enterを聞くことができます

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

次に、コールバック関数内で、コンポーネントの状態を変更する関数、または実行したいことを実行します。


2
「カスタムイベントがReactコンポーネントでネイティブにうまく機能しない」理由について詳しく説明していただけますか?
codeful.element 2017年

1
:@ codeful.elementは、このサイトでは、その上のいくつかの情報があるcustom-elements-everywhere.com/#react
ポール・エベール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.