ReactJS - เพิ่มตัวฟังเหตุการณ์ที่กำหนดเองไปยังคอมโพเนนต์


89

ในจาวาสคริปต์เก่าธรรมดาฉันมี DIV

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

และรหัสจาวาสคริปต์ต่อไปนี้

var myMovie = document.getElementById('my_movie');
myMovie.addEventListener('nv-enter', function (event) {
     console.log('change scope');
});

ตอนนี้ฉันมี React Component อยู่ในส่วนประกอบนี้ในวิธีการเรนเดอร์ฉันกำลังส่งคืน 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:

ป้อนคำอธิบายภาพที่นี่

ฉันใช้แนวคิดทั้งหมดที่ให้ไว้ในคำตอบ ฉันตั้งค่าไลบรารีการนำทางเป็นโหมดดีบักและฉันสามารถนำทางรายการเมนูของฉันได้โดยใช้แป้นพิมพ์เท่านั้น (ดังที่คุณเห็นในภาพหน้าจอฉันสามารถไปที่ภาพยนตร์ 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: ไลบรารีการนำทางนี้ทำงานได้ไม่ดีกับ 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');

@ โดยพื้นฐานแล้วฉันใช้ตัวอย่างจากไฟล์นี้ ( github.com/ahiipsa/navigation/blob/master/demo/index.html )
Thiago

คุณไม่จำเป็นต้องผูกไว้ล่วงหน้าในตัวสร้างของคุณ ( this.handleNVEnter = this.handleNVEnter.bind(this)) และใช้ตัวเริ่มต้นคุณสมบัติ ES7 กับฟังก์ชันลูกศร ( handleNVEnter = enter => {}) เนื่องจากฟังก์ชันลูกศรอ้วนจะถูกผูกไว้เสมอ หากคุณสามารถใช้ไวยากรณ์ ES7 ได้ก็แค่ทำตามนั้น
Aaron Beall

1
ขอบคุณแอรอน ฉันสามารถแก้ไขปัญหาได้ ฉันจะยอมรับคำตอบของคุณเนื่องจากตอนนี้ฉันกำลังใช้วิธีแก้ปัญหาของคุณ แต่ฉันก็ต้องทำอย่างอื่นด้วย เนื่องจากแท็ก HTML ของไลบรารี Nagivation ไม่สามารถเล่นกับ React ได้ดีฉันจึงต้องตั้งชื่อแท็กในการกำหนดค่า lib เพื่อใช้คำนำหน้า aria- * ปัญหาคือเหตุการณ์ถูกทริกเกอร์โดยใช้คำนำหน้าเดียวกันดังนั้นการตั้งค่าเหตุการณ์เป็น aria -nv-enter ทำเคล็ดลับ! ตอนนี้ทำงานได้ดี ขอขอบคุณ!
Thiago

ฉันขอแนะนำให้เปลี่ยนaria-*เป็นdata-*เพราะแอตทริบิวต์ ARIA มาจากชุดมาตรฐานคุณไม่สามารถสร้างขึ้นเองได้ แอตทริบิวต์ข้อมูลสามารถตั้งค่าตามที่คุณต้องการได้โดยพลการ
Marcy Sutton

คำตอบ:


88

หากคุณต้องการจัดการเหตุการณ์ DOM ที่ React ไม่ได้จัดเตรียมไว้ให้คุณต้องเพิ่มตัวรับฟัง 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
คุณกำลัง findDOMNodemisusing ในกรณีของคุณvar elem = this.refs.nv;ก็เพียงพอแล้ว
Pavlo

1
@Pavlo อืมคุณพูดถูกดูเหมือนว่าสิ่งนี้จะเปลี่ยนไปใน v.14 (เพื่อส่งคืนองค์ประกอบ DOM แทนองค์ประกอบ React เหมือนใน v.13 ซึ่งเป็นสิ่งที่ฉันใช้) ขอบคุณ.
Aaron Beall

2
ทำไมฉันต้อง "ตรวจสอบให้แน่ใจว่าได้ลบตัวฟัง DOM ออกเมื่อไม่ได้ต่อเชื่อมส่วนประกอบ" มีแหล่งใดที่พวกเขาจะสร้างการรั่วไหล?
Fen1kz

1
@levininja คลิกที่edited Aug 19 at 6:19ข้อความที่โพสต์ภายใต้ซึ่งจะนำคุณไปประวัติการแก้ไข
Aaron Beall

1
@ NicolasS.Xu React ไม่มี API การจัดส่งเหตุการณ์ที่กำหนดเองใด ๆ เนื่องจากคุณคาดว่าจะใช้อุปกรณ์ประกอบการโทรกลับ (ดูคำตอบนี้ ) แต่คุณสามารถใช้ DOM มาตรฐานได้nv.dispatchEvent()หากต้องการ
Aaron Beall

20

คุณสามารถใช้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;

สวัสดี @vbarbarosh ฉันอัปเดตคำถามพร้อมรายละเอียดเพิ่มเติม
Thiago

4

ก่อนอื่นเหตุการณ์ที่กำหนดเองไม่สามารถเล่นได้ดีกับส่วนประกอบ React โดยกำเนิด คุณจึงไม่สามารถพูด<div onMyCustomEvent={something}>ในฟังก์ชันเรนเดอร์ได้และต้องคิดรอบปัญหา

ประการที่สองหลังจากดูเอกสารสำหรับไลบรารีที่คุณใช้งานแล้วเหตุการณ์จะเริ่มทำงานจริงdocument.bodyดังนั้นแม้ว่าจะได้ผล แต่ตัวจัดการเหตุการณ์ของคุณจะไม่ทริกเกอร์

แต่ภายในcomponentDidMountหนึ่งในแอพลิเคชันของคุณคุณสามารถฟัง NV-ใส่โดยการเพิ่ม

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.