Réagissez pour empêcher les événements de bouillonner dans les composants imbriqués lors d'un clic

115

Voici un élément de base. Les deux <ul>et <li>ont des fonctions onClick. Je veux que seul le onClick sur le <li>se déclenche, pas le <ul>. Comment puis-je atteindre cet objectif?

J'ai joué avec e.preventDefault (), e.stopPropagation (), en vain.

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
la source
J'ai eu la même question, stackoverflow.com/questions/44711549/…

Réponses:

162

J'ai eu le même problème. J'ai trouvé stopPropagation a fait le travail. Je diviserais l'élément de liste en un composant séparé, comme suit:

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
la source
Ne devrait pas être this.props.handleClick()dans le ListItemcomposant this.props.click?
blankface
3
Tout autre moyen sauf stopPropogation?
Ali Sajid
Qu'en est-il du composant natif comme<Link>
samayo
que faire si vous devez passer des paramètres à handleClick?
Manticore le
Cela ne répond-il pas à la mauvaise question? L'OP a demandé que ListItem gère l'événement. La solution a la liste qui la gère. (Sauf si je ne comprends pas quelque chose.)
Thomas Jay Rush
57

React utilise la délégation d'événement avec un seul écouteur d'événement sur le document pour les événements qui bouillonnent, comme «clic» dans cet exemple, ce qui signifie que l'arrêt de la propagation n'est pas possible; l'événement réel s'est déjà propagé au moment où vous interagissez avec lui dans React. stopPropagation sur l'événement synthétique de React est possible car React gère la propagation des événements synthétiques en interne.

stopPropagation: function(e){
    e.stopPropagation();
    e.nativeEvent.stopImmediatePropagation();
}
Priyanshu Chauhan
la source
super utile .. stopPropagation () à lui seul ne fonctionnait pas correctement
pravin
1
C'est ce qui a fonctionné pour moi parce que j'utilise document.addEventListener('click')combiné avec onClick
react
2
Parfois, cela peut ne pas fonctionner - par exemple si vous utilisez une bibliothèque comme Material-UI (www.material-ui.com), ou si vous encapsulez vos gestionnaires dans le rappel d'un autre composant. Mais si votre propre code directement, c'est bien!
rob2d
1
@ rob2d, alors comment y faire face lors de l'utilisation de Material-UI?
Italik
@Italik il y a un exemple de fonction ci-dessus; mais vous devez l'appeler immédiatement comme première chose dans le rappel afin qu'il ne soit pas virtualisé / avalé. Au moins AFAIK. Heureusement, ces derniers temps, je n'ai pas combattu celui-ci.
rob2d
9

Sur l'ordre des événements DOM: CAPTURING vs BUBBLING

Il y a deux étapes pour la façon dont les événements se propagent. Celles-ci sont appelées «capturer» et «bouillonner» .

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

L'étape de capture se produit en premier, puis est suivie de l'étape de bouillonnement. Lorsque vous enregistrez un événement à l'aide de l'API DOM standard, les événements feront partie de l'étape de bouillonnement par défaut, mais cela peut être spécifié lors de la création de l'événement

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

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

Dans React, les événements de bullage sont également ce que vous utilisez par défaut.

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

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

Jetons un œil à l'intérieur de notre 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()  
}

Une alternative que je n'ai pas vue mentionnée ici

Si vous appelez e.preventDefault () dans tous vos événements, vous pouvez vérifier si un événement a déjà été géré et empêcher qu'il ne soit à nouveau traité:

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

  // Perform whatever you need to here.
}

Pour connaître la différence entre les événements synthétiques et les événements natifs, consultez la documentation React: https://reactjs.org/docs/events.html

Tobias Bergkvist
la source
5

Ce n'est pas l'idéal à 100%, mais si c'est trop pénible à transmettre propsaux enfants -> mode pour enfants ou créer un Context.Provider/ Context.Consumer juste à cette fin), et que vous avez affaire à une autre bibliothèque qui a son propre gestionnaire, elle fonctionne avant le vôtre, vous pouvez également essayer:

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

D'après ce que je comprends, la event.persistméthode empêche un objet d'être immédiatement renvoyé dans le SyntheticEventpool de React . Donc, à cause de cela, le eventpassé dans React n'existe pas au moment où vous l'atteignez! Cela se produit chez les petits-enfants à cause de la façon dont React gère les choses en interne en vérifiant d'abord parent activé pour les SyntheticEventgestionnaires (surtout si le parent avait un rappel).

Tant que vous ne faites pas appel persistà quelque chose qui créerait une mémoire importante pour continuer à créer des événements tels que onMouseMove(et que vous ne créez pas une sorte de jeu Cookie Clicker comme Grandma's Cookies), cela devrait être parfaitement bien!

Notez également: en lisant occasionnellement autour de leur GitHub, nous devrions garder les yeux ouverts pour les futures versions de React car elles pourraient éventuellement résoudre une partie de la douleur avec cela car elles semblent aller vers le pliage du code React dans un compilateur / transpilateur.

rob2d
la source
1

J'ai eu du mal à event.stopPropagation()travailler. Si vous le faites aussi, essayez de le déplacer vers le haut de votre fonction de gestionnaire de clics, c'est ce que je devais faire pour empêcher l'événement de bouillonner. Exemple de fonction:

  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;
    }
  }
Joël Peltonen
la source
0

Vous pouvez éviter le bouillonnement d'événement en vérifiant la cible de l'événement.
Par exemple, si vous avez une entrée imbriquée dans l'élément div où vous avez un gestionnaire pour l'événement de clic, et que vous ne voulez pas le gérer, lorsque vous cliquez sur l'entrée, vous pouvez simplement passer event.targetdans votre gestionnaire et vérifier si le gestionnaire doit être exécuté en fonction de propriétés de la cible.
Par exemple, vous pouvez vérifier if (target.localName === "input") { return}.
Donc, c'est un moyen "d'éviter" l'exécution du gestionnaire

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

La nouvelle façon de procéder est beaucoup plus simple et vous fera gagner du temps! Passez simplement l'événement dans le gestionnaire de clic d'origine et appelez preventDefault();.

clickHandler(e){
    e.preventDefault();
    //Your functionality here
}
Austin Rowe
la source
3
-1; J'ai testé cela et cela ne fonctionne pas pour la propagation des clics. AFAIK cela sera utile si vous voulez un gestionnaire onClick pour les liens qui arrêtent la fonctionnalité de lien de base, mais pas pour le bullage d'événements.
Joel Peltonen le