ReactJS-컴포넌트에 커스텀 이벤트 리스너 추가


87

평범한 오래된 자바 스크립트에서는 DIV가 있습니다.

<div class="movie" id="my_movie">

및 다음 자바 스크립트 코드

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

@The 나는 기본적으로이 파일의 예제를 사용하고 있습니다 ( github.com/ahiipsa/navigation/blob/master/demo/index.html )
Thiago

생성자 ( this.handleNVEnter = this.handleNVEnter.bind(this)) 에서 미리 바인딩 하고 화살표 함수 ( handleNVEnter = enter => {}) 와 함께 ES7 속성 이니셜 라이저를 사용할 필요가 없습니다. 굵은 화살표 함수는 항상 바인딩되어 있기 때문입니다. ES7 구문을 사용할 수 있다면 그대로 사용하십시오.
아론 Beall

1
Aaron 감사합니다. 문제를 해결할 수있었습니다. 나는 지금 귀하의 솔루션을 사용하고 있기 때문에 귀하의 대답을 수락 할 것이지만 다른 작업도 수행해야했습니다. Nagivation 라이브러리 HTML 태그는 React에서 잘 작동하지 않기 때문에 lib 구성에서 aria- * 접두사를 사용하도록 태그 이름을 설정해야했습니다. 문제는 이벤트가 동일한 접두사를 사용하여 트리거되었으므로 이벤트를 aria로 설정하는 것입니다. -nv-enter가 트릭을했습니다! 이제 잘 작동합니다. 감사합니다!
티아고

ARIA 속성은 표준 세트에서 왔기 때문에로 변경 aria-*하는 것이 좋습니다 data-*. 직접 구성 할 수 없습니다. 데이터 속성은 원하는대로 더 임의로 설정할 수 있습니다.
Marcy Sutton

답변:


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;에는 충분합니다.
Pavlo

1
@Pavlo Hm, 당신이 맞습니다, 이것이 v.14에서 변경된 것 같습니다 (v.13에서와 같이 React 요소 대신 DOM 요소를 반환하기 위해). 감사.
아론 Beall

2
"컴포넌트가 마운트 해제 될 때 DOM 리스너를 제거해야합니다"라는 이유는 무엇입니까? 그들이 누출을 일으킬 소스가 있습니까?
Fen1kz

1
@levininja edited Aug 19 at 6:19게시물 아래 의 텍스트를 클릭하면 업데이트 기록으로 이동 합니다.
Aaron Beall 2017

1
@ NicolasS.Xu React는 콜백 소품을 사용할 것으로 예상되는 사용자 지정 이벤트 디스패치 API를 제공하지 않지만 ( 이 답변 참조 ) nv.dispatchEvent()필요한 경우 표준 DOM을 사용할 수 있습니다 .
Aaron Beall

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 안녕하세요, 자세한 내용과 질문을 업데이트
티아고

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.