ทำปฏิกิริยาป้องกันไม่ให้เกิดเหตุการณ์เดือดในส่วนประกอบที่ซ้อนกันเมื่อคลิก


116

นี่คือส่วนประกอบพื้นฐาน ทั้งฟังก์ชัน onClick <ul>และ<li>มี ฉันต้องการเฉพาะ onClick บน<li>ไฟไม่ใช่<ul>. ฉันจะบรรลุเป้าหมายนี้ได้อย่างไร?

ฉันเล่นกับ e.preventDefault (), e.stopPropagation () ไปแล้ว

class List extends React.Component {
  constructor(props) {
    super(props);
  }

  handleClick() {
    // do something
  }

  render() {

    return (
      <ul 
        onClick={(e) => {
          console.log('parent');
          this.handleClick();
        }}
      >
        <li 
          onClick={(e) => {
            console.log('child');
            // prevent default? prevent propagation?
            this.handleClick();
          }}
        >
        </li>       
      </ul>
    )
  }
}

// => parent
// => child

ฉันมีคำถามเดียวกันstackoverflow.com/questions/44711549/…

คำตอบ:


163

ฉันมีปัญหาเดียวกัน ฉันพบว่า stopPropagation ได้ผล ฉันจะแยกรายการออกเป็นองค์ประกอบแยกต่างหากดังนี้:

class List extends React.Component {
  handleClick = e => {
    // do something
  }

  render() {
    return (
      <ul onClick={this.handleClick}>
        <ListItem onClick={this.handleClick}>Item</ListItem> 
      </ul>
    )
  }
}

class ListItem extends React.Component {
  handleClick = e => {
    e.stopPropagation();  //  <------ Here is the magic
    this.props.onClick();
  }

  render() {
    return (
      <li onClick={this.handleClick}>
        {this.props.children}
      </li>       
    )
  }
}

ไม่ควรthis.props.handleClick()อยู่ในListItemองค์ประกอบthis.props.click?
blankface

3
วิธีอื่นใดนอกจาก stopPropogation?
Ali Sajid

เกี่ยวกับองค์ประกอบดั้งเดิมเช่น<Link>
samayo

จะเกิดอะไรขึ้นถ้าคุณต้องส่งผ่านพารามิเตอร์เพื่อจัดการคลิก?
มันติคอร์

นี่ตอบคำถามผิดไม่ใช่เหรอ? OP ขอให้ ListItem จัดการเหตุการณ์ วิธีแก้ปัญหามีรายการจัดการ (ยกเว้นกรณีที่ฉันเข้าใจผิด)
Thomas Jay Rush

57

React ใช้การมอบหมายเหตุการณ์กับผู้ฟังเหตุการณ์เดียวในเอกสารสำหรับเหตุการณ์ที่เกิดฟองเช่น 'คลิก' ในตัวอย่างนี้ซึ่งหมายความว่าไม่สามารถหยุดการเผยแพร่ได้ เหตุการณ์จริงได้แพร่กระจายไปแล้วตามเวลาที่คุณโต้ตอบใน React stopPropagation เกี่ยวกับเหตุการณ์สังเคราะห์ของ React เป็นไปได้เนื่องจาก React จัดการการแพร่กระจายของเหตุการณ์สังเคราะห์ภายใน

stopPropagation: function(e){
    e.stopPropagation();
    e.nativeEvent.stopImmediatePropagation();
}

มีประโยชน์สุด ๆ .. stopPropagation () เองทำงานไม่ถูกต้อง
pravin

1
นี่คือสิ่งที่ได้ผลสำหรับฉันเพราะฉันใช้document.addEventListener('click')ร่วมกับ react'sonClick
OtotheA

2
บางครั้งสิ่งนี้อาจใช้ไม่ได้ตัวอย่างเช่นหากคุณใช้ไลบรารีเช่น Material-UI (www.material-ui.com) หรือรวมตัวจัดการของคุณไว้ในการเรียกกลับของคอมโพเนนต์อื่น แต่ถ้ารหัสของคุณเองโดยตรงก็ดี!
rob2d

1
@ rob2d ดังนั้นจะจัดการกับมันอย่างไรเมื่อใช้ Material-UI?
Italik

@Italik มีฟังก์ชันตัวอย่างด้านบน แต่คุณต้องเรียกมันทันทีเป็นสิ่งแรกในการโทรกลับเพื่อไม่ให้ถูกจำลอง / กลืน อย่างน้อย AFAIK โชคดีที่ไม่ได้ต่อสู้กับสิ่งนี้
rob2d

9

ตามลำดับเหตุการณ์ DOM: CAPTURING vs BUBBLING

มีสองขั้นตอนในการเผยแพร่เหตุการณ์ เหล่านี้เรียกว่า"จับ"และ"ฟอง"

               | |                                   / \
---------------| |-----------------   ---------------| |-----------------
| element1     | |                |   | element1     | |                |
|   -----------| |-----------     |   |   -----------| |-----------     |
|   |element2  \ /          |     |   |   |element2  | |          |     |
|   -------------------------     |   |   -------------------------     |
|        Event CAPTURING          |   |        Event BUBBLING           |
-----------------------------------   -----------------------------------

ขั้นตอนการจับภาพจะเกิดขึ้นก่อนแล้วตามด้วยขั้นตอนที่มีฟอง เมื่อคุณลงทะเบียนเหตุการณ์โดยใช้ DOM api ปกติเหตุการณ์จะเป็นส่วนหนึ่งของขั้นตอนการเดือดตามค่าเริ่มต้น แต่สามารถระบุได้เมื่อสร้างเหตุการณ์

// CAPTURING event
button.addEventListener('click', handleClick, true)

// BUBBLING events
button.addEventListener('click', handleClick, false)
button.addEventListener('click', handleClick)

ในการตอบสนองเหตุการณ์ที่มีฟองยังเป็นสิ่งที่คุณใช้โดยค่าเริ่มต้น

// handleClick is a BUBBLING (synthetic) event
<button onClick={handleClick}></button>

// handleClick is a CAPTURING (synthetic) event
<button onClickCapture={handleClick}></button>

ลองมาดูในที่จับของเราคลิกโทรกลับ (ตอบสนอง):

function handleClick(e) {
  // This will prevent any synthetic events from firing after this one
  e.stopPropagation()
}
function handleClick(e) {
  // This will set e.defaultPrevented to true
  // (for all synthetic events firing after this one)
  e.preventDefault()  
}

ทางเลือกที่ฉันไม่เคยเห็นมาก่อน

หากคุณเรียกใช้ e.preventDefault () ในกิจกรรมทั้งหมดของคุณคุณสามารถตรวจสอบว่าเหตุการณ์ได้รับการจัดการหรือไม่และป้องกันไม่ให้จัดการอีกครั้ง:

handleEvent(e) {
  if (e.defaultPrevented) return  // Exits here if event has been handled
  e.preventDefault()

  // Perform whatever you need to here.
}

สำหรับความแตกต่างระหว่างเหตุการณ์สังเคราะห์และเหตุการณ์เนทีฟโปรดดูเอกสาร React: https://reactjs.org/docs/events.html


5

สิ่งนี้ไม่เหมาะ 100% แต่ถ้าเป็นความเจ็บปวดมากเกินไปที่จะส่งต่อpropsเด็ก ๆ -> แฟชั่นเด็ก ๆ หรือสร้างContext.Provider/ Context.Consumer เพียงเพื่อจุดประสงค์นี้) และคุณกำลังจัดการกับห้องสมุดอื่นที่มีตัวจัดการของตัวเอง ก่อนของคุณคุณสามารถลอง:

   function myHandler(e) { 
        e.persist(); 
        e.nativeEvent.stopImmediatePropagation();
        e.stopPropagation(); 
   }

จากสิ่งที่ฉันเข้าใจevent.persistวิธีการนี้จะป้องกันไม่ให้วัตถุถูกโยนกลับเข้าไปในSyntheticEventสระว่ายน้ำของ React ทันที ด้วยเหตุนี้การeventส่งผ่านใน React จึงไม่มีอยู่จริงเมื่อคุณไปถึงมัน! สิ่งนี้เกิดขึ้นในรุ่นหลานเนื่องจากวิธีที่ React จัดการกับสิ่งต่างๆภายในโดยการตรวจสอบพาเรนต์ก่อนสำหรับSyntheticEventตัวจัดการ (โดยเฉพาะอย่างยิ่งถ้าผู้ปกครองมีการโทรกลับ)

ตราบใดที่คุณไม่ได้เรียกร้องpersistอะไรบางอย่างที่จะสร้างความทรงจำที่สำคัญเพื่อสร้างกิจกรรมต่อไปเช่นonMouseMove(และคุณไม่ได้สร้างเกม Cookie Clicker บางประเภทเช่นคุณย่าของคุกกี้) ก็น่าจะดีมาก!

นอกจากนี้โปรดทราบ: จากการอ่าน GitHub ในบางครั้งเราควรจับตาดู React รุ่นต่อ ๆ ไปเพราะในที่สุดพวกเขาอาจแก้ปัญหานี้ได้เนื่องจากพวกเขาดูเหมือนจะทำโค้ด React แบบพับในคอมไพเลอร์ / ทรานสไพเลอร์


1

ฉันมีปัญหาในการevent.stopPropagation()ทำงาน ถ้าคุณทำเช่นนั้นให้ลองย้ายไปที่ด้านบนสุดของฟังก์ชันตัวจัดการคลิกนั่นคือสิ่งที่ฉันต้องทำเพื่อหยุดเหตุการณ์ไม่ให้เดือด ตัวอย่างฟังก์ชัน:

  toggleFilter(e) {
    e.stopPropagation(); // If moved to the end of the function, will not work
    let target = e.target;
    let i = 10; // Sanity breaker

    while(true) {
      if (--i === 0) { return; }
      if (target.classList.contains("filter")) {
        target.classList.toggle("active");
        break;
      }
      target = target.parentNode;
    }
  }

0

คุณสามารถหลีกเลี่ยงเหตุการณ์ที่เดือดปุด ๆ ได้โดยตรวจสอบเป้าหมายของเหตุการณ์
ตัวอย่างเช่นหากคุณป้อนข้อมูลที่ซ้อนอยู่กับองค์ประกอบ div ที่คุณมีตัวจัดการสำหรับเหตุการณ์การคลิกและคุณไม่ต้องการจัดการกับเหตุการณ์นั้นเมื่อมีการคลิกอินพุตคุณสามารถส่งผ่านevent.targetไปยังตัวจัดการของคุณและตัวจัดการการตรวจสอบควรดำเนินการตาม คุณสมบัติของเป้าหมาย ตัวอย่างเช่นคุณสามารถตรวจสอบ
ดังนั้นจึงเป็นวิธี "หลีกเลี่ยง" การดำเนินการของตัวจัดการif (target.localName === "input") { return}


-4

วิธีใหม่ในการทำเช่นนี้ง่ายกว่ามากและจะช่วยคุณประหยัดเวลา! preventDefault();เพียงแค่ผ่านเหตุการณ์ลงในตัวจัดการคลิกเดิมและโทร

clickHandler(e){
    e.preventDefault();
    //Your functionality here
}

3
-1; ฉันทดสอบแล้วและไม่ได้ผลกับการขยายการคลิก AFAIK สิ่งนี้จะมีประโยชน์หากคุณต้องการตัวจัดการ onClick สำหรับลิงก์ที่หยุดการทำงานของลิงก์พื้นฐาน แต่ไม่ใช่สำหรับเหตุการณ์ที่เดือดปุด ๆ
Joel Peltonen
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.