ReactJS - Добавить пользовательский прослушиватель событий для компонента
В простой старый 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, внутри этого компонента, в методе рендеринга, я возвращаю свой div. Как я могу добавить прослушиватель событий для своего пользовательского события? (Я использую эту библиотеку для телевизионных приложений - https://github.com/ahiipsa/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: эта библиотека навигации не работает с 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');
5 ответов
Если вам нужно обработать события DOM, еще не предоставленные React, вам нужно добавить прослушиватели DOM после монтирования компонента:
Обновление: между Реактом 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>
);
}
}
Вы можете использовать методы 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;
Во-первых, пользовательские события не очень хорошо работают с компонентами React. Так что вы не можете просто сказать, <div onMyCustomEvent={something}>
в функции рендера, и должны обдумать проблему.
Во-вторых, после ознакомления с документацией по используемой вами библиотеке событие на самом деле запускается. document.body
, так что даже если бы это сработало, ваш обработчик событий никогда не сработал бы.
Вместо этого внутри componentDidMount
где-то в вашем приложении вы можете прослушать nv-enter, добавив
document.body.addEventListener('nv-enter', function (event) {
// logic
});
Затем, внутри функции обратного вызова, нажмите на функцию, которая изменяет состояние компонента или все, что вы хотите сделать.
Вот
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 }}));
}
Как только это будет сделано, у вас будет другой компонент, который должен будет прослушивать событие переключения языка. Например, ваш клиент пользуется данным продуктом, и вы обновите продукт, указав новый язык в качестве аргумента.
Сначала добавьте/удалите прослушиватель событий для вашего целевого компонента:
componentDidMount() {
document.body.addEventListener('my-set-lang', this.handleLangChange.bind(this));
}
componentWillUnmount() {
document.body.removeEventListener('my-set-lang', this.handleLangChange.bind(this));
}
затем определите свой компонент
my-set-langw
обработчик
handleLangChange(event) {
console.log("lang has changed to", event.detail.newLang);
// your business logic here .. this.setState({...});
}
Я рекомендую использовать
React.createRef()
и
ref=this.elementRef
чтобы получить ссылку на элемент DOM вместо
ReactDOM.findDOMNode(this)
. Таким образом вы можете получить ссылку на элемент 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;