Реагировать на предотвращение всплытия событий во вложенных компонентах при нажатии

116

Вот базовый компонент. И у <ul>и <li>есть функции onClick. Я хочу, чтобы запускался только 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
dmwong2268
источник
У меня был такой же вопрос, stackoverflow.com/questions/44711549/…

Ответы:

164

Я была такая же проблема. Я обнаружил, что 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?
Али Саджид
Как насчет нативного компонента, такого как<Link>
Samayo 06
что, если вам нужно передать параметры в handleClick?
Manticore
Разве это не ответ на неправильный вопрос? OP запросил ListItem для обработки события. В решении есть список, обрабатывающий его. (Если я чего-то не понимаю.)
Томас Джей Раш,
57

React использует делегирование событий с одним прослушивателем событий в документе для всплывающих событий, таких как «щелчок» в этом примере, что означает, что остановка распространения невозможна; реальное событие уже распространено к тому моменту, когда вы взаимодействуете с ним в React. stopPropagation в синтетическом событии React возможно, потому что React обрабатывает распространение синтетических событий внутри себя.

stopPropagation: function(e){
    e.stopPropagation();
    e.nativeEvent.stopImmediatePropagation();
}
Прияншу Чаухан
источник
супер полезно .. stopPropagation () сам по себе не работал должным образом
правин
1
Это то, что сработало для меня, потому что я использую в document.addEventListener('click')сочетании с onClick
react
2
Иногда это может не сработать - например, если вы используете такую ​​библиотеку, как Material-UI (www.material-ui.com), или обертываете свои обработчики в обратном вызове другого компонента. Но если ваш собственный код напрямую, это нормально!
rob2d 03
1
@ rob2d, так как с этим бороться при использовании Material-UI?
Италик,
@Italik есть пример функции выше; но вам нужно вызвать его немедленно как первое действие в обратном вызове, чтобы он не был виртуализирован / проглочен. По крайней мере, AFAIK. К счастью в последнее время не боролся с этим.
rob2d
9

По порядку событий DOM: ЗАХВАТ vs ЗАПУСК

Есть два этапа распространения событий. Это называется «захват» и «всплытие» .

               | |                                   / \
---------------| |-----------------   ---------------| |-----------------
| 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)

В React всплывающие события также используются по умолчанию.

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

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

Заглянем внутрь нашего обратного вызова 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()  
}

Альтернатива, которую я здесь не видел

Если вы вызываете 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, такую ​​как Cookies бабушки), все должно быть в полном порядке!

Также обратите внимание: время от времени читая их GitHub, мы должны следить за будущими версиями React, поскольку они могут в конечном итоге решить некоторые проблемы с этим, поскольку они, похоже, собираются сделать складной код React в компиляторе / транспиляторе.

rob2d
источник
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