Bereaksi mencegah peristiwa menggelembung di komponen bersarang saat diklik

115

Berikut adalah komponen dasarnya. Baik <ul>dan <li>memiliki fungsi onClick. Saya hanya ingin onClick yang <li>menyala, bukan <ul>. Bagaimana saya bisa mencapai ini?

Saya telah bermain-main dengan e.preventDefault (), e.stopPropagation (), tetapi tidak berhasil.

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
dmwong2268.dll
sumber
Saya memiliki pertanyaan yang sama, stackoverflow.com/questions/44711549/…

Jawaban:

162

Saya memiliki masalah yang sama. Saya menemukan stopPropagation melakukan pekerjaan. Saya akan membagi item daftar menjadi komponen terpisah, sebagai berikut:

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>       
    )
  }
}
Will Stone
sumber
Bukankah seharusnya this.props.handleClick()dalam ListItemkomponen this.props.click?
blankface
3
Adakah cara lain selain stopPropogation?
Ali Sajid
Bagaimana dengan komponen asli seperti<Link>
samayo
bagaimana jika Anda perlu meneruskan parameter ke handleClick?
Manticore
Bukankah ini menjawab pertanyaan yang salah? OP meminta ListItem untuk menangani acara tersebut. Solusinya memiliki Daftar yang menanganinya. (Kecuali saya salah paham tentang sesuatu.)
Thomas Jay Rush
57

React menggunakan event delegation dengan satu event listener pada dokumen untuk event yang menggelembung, seperti 'click' dalam contoh ini, yang berarti menghentikan propagasi tidak dimungkinkan; acara nyata telah disebarkan pada saat Anda berinteraksi dengannya di React. stopPropagation pada kejadian sintetis React dimungkinkan karena React menangani penyebaran kejadian sintetis secara internal.

stopPropagation: function(e){
    e.stopPropagation();
    e.nativeEvent.stopImmediatePropagation();
}
Priyanshu Chauhan
sumber
sangat berguna .. stopPropagation () sendiri tidak berfungsi dengan benar
pravin
1
Inilah yang berhasil untuk saya karena saya menggunakan document.addEventListener('click')dikombinasikan dengan react'sonClick
OtotheA
2
Terkadang ini mungkin tidak berfungsi - misalnya jika Anda menggunakan pustaka seperti Material-UI (www.material-ui.com), atau menggabungkan penangan Anda dalam callback komponen lain. Tapi kalau langsung kodenya sendiri, oke lah!
rob2d
1
@ rob2d, jadi bagaimana cara menghadapinya saat menggunakan Material-UI?
Cetak miring
@ Italik ada fungsi contoh di atas; tetapi Anda harus segera memanggilnya sebagai hal pertama dalam callback agar tidak divirtualisasi / ditelan. Setidaknya AFAIK. Syukurlah akhir-akhir ini belum ada pertarungan yang satu ini.
rob2d
9

Di urutan peristiwa DOM: MENANGKAP vs BUBBLING

Ada dua tahap bagaimana peristiwa disebarkan. Ini disebut "menangkap" dan "menggelegak" .

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

Tahap penangkapan terjadi terlebih dahulu, kemudian dilanjutkan dengan tahap penggelembungan. Saat Anda mendaftarkan acara menggunakan api DOM reguler, acara tersebut akan menjadi bagian dari tahap menggelembung secara default, tetapi ini dapat ditentukan saat pembuatan acara

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

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

Di React, acara menggelegak juga yang Anda gunakan secara default.

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

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

Mari kita lihat di dalam callback handleClick (React):

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()  
}

Alternatif yang belum saya lihat disebutkan di sini

Jika Anda memanggil e.preventDefault () di semua acara Anda, Anda bisa memeriksa apakah sebuah acara telah ditangani, dan mencegahnya ditangani lagi:

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

  // Perform whatever you need to here.
}

Untuk perbedaan antara kejadian sintetis dan kejadian asli, lihat dokumentasi React: https://reactjs.org/docs/events.html

Tobias Bergkvist
sumber
5

Ini tidak 100% ideal, tetapi jika terlalu menyakitkan untuk diturunkan propspada anak-anak -> busana anak-anak atau buat Context.Provider/ Context.Consumer hanya untuk tujuan ini), dan Anda berurusan dengan perpustakaan lain yang memiliki penangannya sendiri yang menjalankannya sebelum milik Anda, Anda juga dapat mencoba:

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

Dari apa yang saya pahami, event.persistmetode ini mencegah sebuah objek segera dilemparkan kembali ke SyntheticEventpool React . Jadi karena itu, eventpass di React sebenarnya tidak ada saat Anda meraihnya! Ini terjadi pada cucu karena cara React menangani masalah internal dengan terlebih dahulu memeriksa orang tuaSyntheticEvent penangannya (terutama jika orang tua memiliki panggilan balik).

Selama Anda tidak memanggil persistsesuatu yang akan menciptakan memori yang signifikan untuk terus membuat acara sepertionMouseMove (dan Anda tidak membuat semacam permainan Cookie Clicker seperti Kue Nenek), itu akan baik-baik saja!

Juga perhatikan: dari membaca sekitar GitHub mereka sesekali, kita harus terus mengawasi versi React yang akan datang karena mereka pada akhirnya dapat menyelesaikan beberapa masalah dengan ini karena mereka tampaknya akan membuat kode React lipat dalam kompiler / transpiler.

rob2d
sumber
1

Saya mengalami masalah dalam event.stopPropagation()bekerja. Jika Anda juga melakukannya, coba pindahkan ke bagian atas fungsi handler klik Anda, itulah yang perlu saya lakukan untuk menghentikan acara menggelembung. Fungsi contoh:

  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;
    }
  }
Joel Peltonen
sumber
0

Anda dapat menghindari acara menggelembung dengan memeriksa target acara.
Misalnya jika Anda memiliki masukan yang bersarang ke elemen div di mana Anda memiliki penangan untuk peristiwa klik, dan Anda tidak ingin menanganinya, saat masukan diklik, Anda bisa meneruskan event.targetke penangan Anda dan memeriksa apakah penangan harus dijalankan berdasarkan properti target.
Misalnya Anda dapat memeriksa if (target.localName === "input") { return}.
Jadi, ini adalah cara untuk "menghindari" eksekusi penangan

Роман Олизаревич
sumber
-4

Cara baru untuk melakukan ini jauh lebih sederhana dan akan menghemat waktu Anda! Cukup teruskan peristiwa tersebut ke penangan klik asli dan panggil preventDefault();.

clickHandler(e){
    e.preventDefault();
    //Your functionality here
}
Austin Rowe
sumber
3
-1; Saya menguji ini dan tidak berhasil untuk propagasi klik. AFAIK ini akan berguna jika Anda menginginkan pengendali onClick untuk tautan yang menghentikan fungsi tautan dasar, tetapi tidak untuk acara menggelegak.
Joel Peltonen