Reaguj, aby zapobiec propagowaniu zdarzeń w zagnieżdżonych komponentach po kliknięciu

115

Oto podstawowy element. Obie funkcje <ul>i <li>mają onClick. Chcę tylko onClick, <li>aby odpalić, a nie <ul>. Jak mogę to osiągnąć?

Bawiłem się z e.preventDefault (), e.stopPropagation (), ale bezskutecznie.

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
źródło

Odpowiedzi:

162

Miałem ten sam problem. Okazało się, że stopPropagation zadziałało. Podzieliłbym element listy na osobny komponent, tak jak:

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
źródło
Nie powinno być this.props.handleClick()w ListItemkomponencie this.props.click?
blankface
3
Jakikolwiek inny sposób poza stopPropogation?
Ali Sajid,
A co z komponentem natywnym, takim jak<Link>
samayo
co jeśli musisz przekazać parametry do handleClick?
Manticore,
Czy to nie odpowiada na złe pytanie? OP poprosił o ListItem do obsługi zdarzenia. Rozwiązanie obsługuje listę. (Chyba że coś nie rozumiem.)
Thomas Jay Rush
57

React używa delegowania zdarzeń z jednym nasłuchiwaniem w dokumencie dla zdarzeń, które są w dymku, jak np. „Kliknięcie” w tym przykładzie, co oznacza, że ​​zatrzymanie propagacji nie jest możliwe; rzeczywiste wydarzenie zostało już rozpropagowane do czasu interakcji z nim w Reakcie. stopPropagation na syntetycznym zdarzeniu Reacta jest możliwe, ponieważ React wewnętrznie obsługuje propagację zdarzeń syntetycznych.

stopPropagation: function(e){
    e.stopPropagation();
    e.nativeEvent.stopImmediatePropagation();
}
Priyanshu Chauhan
źródło
super przydatne .. funkcja stopPropagation () sama nie działała poprawnie
pravin
1
To właśnie zadziałało dla mnie, ponieważ używam document.addEventListener('click')kombinacji z onClick
reakcjami
2
Czasami może to nie działać - na przykład, jeśli używasz biblioteki takiej jak Material-UI (www.material-ui.com) lub pakujesz swoje programy obsługi w wywołaniu zwrotnym innego składnika. Ale jeśli masz własny kod bezpośrednio, jest w porządku!
rob2d
1
@ rob2d, więc jak sobie z tym poradzić korzystając z Material-UI?
Italik
@Italik powyżej jest przykładowa funkcja; ale musisz wywołać to natychmiast jako pierwszą rzecz w wywołaniu zwrotnym, aby nie został zwirtualizowany / połknięty. Przynajmniej AFAIK. Na szczęście ostatnio nie walczyłem z tym.
rob2d
9

W kolejności zdarzeń DOM: CAPTURING vs BUBBLING

Istnieją dwa etapy propagacji wydarzeń. Nazywa się to „przechwytywaniem” i „bulgotaniem” .

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

Najpierw następuje etap przechwytywania, a po nim faza bulgotania. Gdy rejestrujesz zdarzenie przy użyciu zwykłego interfejsu API DOM, zdarzenia będą domyślnie częścią etapu propagacji, ale można to określić podczas tworzenia zdarzenia

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

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

W Reakcie zdarzenia bulgotania są również używane domyślnie.

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

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

Rzućmy okiem na nasz uchwyt handleClick callback (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()  
}

Alternatywa, o której tutaj nie wspomniałem

Jeśli wywołasz e.preventDefault () we wszystkich swoich zdarzeniach, możesz sprawdzić, czy zdarzenie zostało już obsłużone i uniemożliwić ponowne obsłużenie:

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

  // Perform whatever you need to here.
}

Aby zobaczyć różnicę między zdarzeniami syntetycznymi i natywnymi, zobacz dokumentację React: https://reactjs.org/docs/events.html

Tobias Bergkvist
źródło
5

Nie jest to w 100% idealne, ale jeśli jest to zbyt trudne do przekazania propsdzieciom -> moda dziecięca lub tworzenie Context.Provider/ Context.Consumer tylko w tym celu), a masz do czynienia z inną biblioteką, która ma własny program obsługi, który działa przed Twoim możesz też spróbować:

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

Z tego, co rozumiem, event.persistmetoda zapobiega natychmiastowemu wrzuceniu obiektu z powrotem do SyntheticEventpuli Reacta . Z tego powodu eventprzekazany w Reactcie właściwie nie istnieje, zanim po niego sięgniesz! Dzieje się tak u wnuków ze względu na sposób, w jaki React radzi sobie z rzeczami wewnętrznie, najpierw sprawdzając, czy rodzic nie ma SyntheticEventobsługi (zwłaszcza jeśli rodzic miał wywołanie zwrotne).

Tak długo, jak nie wywołujesz persistczegoś, co stworzyłoby znaczną pamięć do tworzenia wydarzeń, takich jak onMouseMove(i nie tworzysz jakiejś gry typu Cookie Clicker, takiej jak Grandma's Cookies), wszystko powinno być w porządku!

Uwaga: czytając od czasu do czasu ich GitHub, powinniśmy wypatrywać przyszłych wersji Reacta, ponieważ mogą one ostatecznie rozwiązać niektóre problemy związane z tym, ponieważ wydaje się, że zmierzają do składania kodu React w kompilatorze / transpilerze.

rob2d
źródło
1

Miałem problemy z rozpoczęciem event.stopPropagation()pracy. Jeśli to zrobisz, spróbuj przenieść go na górę funkcji obsługi kliknięcia, to właśnie musiałem zrobić, aby zatrzymać bulgotanie zdarzenia. Przykładowa funkcja:

  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
źródło
0

Możesz uniknąć bulgotania zdarzenia, sprawdzając cel zdarzenia.
Na przykład, jeśli masz dane wejściowe zagnieżdżone w elemencie div, w którym masz moduł obsługi zdarzenia kliknięcia, i nie chcesz go obsługiwać, po kliknięciu wejścia możesz po prostu przekazać event.targetdo swojego modułu obsługi i sprawdzić, czy funkcja obsługi powinna zostać wykonana na podstawie właściwości celu.
Na przykład możesz sprawdzić if (target.localName === "input") { return}.
Jest to więc sposób na „uniknięcie” wykonania procedury obsługi

Роман Олизаревич
źródło
-4

Nowy sposób jest dużo prostszy i pozwoli Ci zaoszczędzić trochę czasu! Po prostu przekaż zdarzenie do oryginalnego modułu obsługi kliknięć i wywołaj preventDefault();.

clickHandler(e){
    e.preventDefault();
    //Your functionality here
}
Austin Rowe
źródło
3
-1; Przetestowałem to i nie działa w przypadku propagacji kliknięć. AFAIK będzie to przydatne, jeśli chcesz obsługiwać onClick dla linków, które zatrzymują podstawową funkcjonalność linków, ale nie do propagowania zdarzeń.
Joel Peltonen