Реагировать на проблему окончания анимации

Я делаю простую службу уведомлений в React и столкнулся с очень странной проблемой, о которой я не могу найти ни упоминания, ни решения.

Мой компонент имеет массив сообщений уведомлений. Каждое «уведомление» имеет привязку «onClick» и «onAnimationEnd», которая вызывает функцию, которая удалит их из массива уведомлений. Основная идея заключается в том, что уведомление исчезнет (с помощью CSS-анимации), а затем будет удалено или позволит пользователю вручную щелкнуть уведомление, чтобы удалить его.

Интересный баг заключается в следующем. Если вы добавите два уведомления, первое вызовет onAnimationEnd и удалит себя. Оставшееся уведомление внезапно перейдет к концу своей CSS-анимации и никогда не вызовет собственное onAnimationEnd.

Еще более любопытно, что если вы добавите четыре уведомления, ровно два из них будут иметь указанную выше ошибку, а два других будут работать правильно.

Стоит также отметить, что если вы создадите два уведомления и щелкнете по одному из двух вручную, чтобы удалить его, оставшееся уведомление будет работать нормально и запускать свою собственную функциональность onAnimationEnd.

Таким образом, я вынужден сделать вывод, что какая-то комбинация циклического обхода массива и триггера onAnimationEnd каким-то образом прослушивается в реакции, если только кто-то не может указать решение этой проблемы.

Код реакции

class Test extends React.Component {
    constructor (props) {
    super(props)
    this.state = {
      notifications: []
    }
  }

  add = () => {
    this.setState({notifications: [...this.state.notifications, 'Test']})
  }

  remove = (notification) => {
    let notificationArray = this.state.notifications
    notificationArray.splice(notificationArray.indexOf(notification), 1)
    this.setState({notifications: notificationArray})
  }

  render() {
    return (
      <div>
        <button onClick = {this.add}>Add Notification</button>
        <div className = "notification-container">
        {this.state.notifications.map(
          (notification,i) => {
            return (
              <div onAnimationEnd = {()=>this.remove(notification)} 
                   onClick = {() => this.remove(notification)}
                   className = "notification" 
                   key = {i}>
                   {notification}
              </div>
            )
          }
        )}
        </div>
      </div>
    );
  }
}


ReactDOM.render(
  <Test />,
  document.getElementById('root')
);

CSS

.notification-container {
  position: fixed;
  bottom: 20px;
  right: 20px;
  width: 200px;
}

.notification {
  border: 1px solid;
  box-shadow: 0 10px 20px rgba(0,0,0,0.2), 0 6px 6px rgba(0,0,0,0.25);
  color: white;
  background: rgba(0,0,0,0.75);
  cursor: pointer;
  padding: 10px;
  margin-top: 10px;
  user-select: none;
  animation: fade 7s linear forwards;
}

@keyframes fade {
  0% {
    transform: translate(100%,0);
  }
  2% {
    transform: translate(-20px, 0);
  }
  5% {
    transform: translate(0,0);
  }
  20% {
    opacity: 1;
  }
  100% {
    opacity: 0.25;
  }
}

Рабочая ссылка на кодепен https://codepen.io/msorrentino/pen/xeVrwz

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
3
0
9 426
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы используете индекс массива в качестве ключа компонента:

        {this.state.notifications.map(
          (notification,i) => {
            return (
              <div onAnimationEnd = {()=>this.remove(notification)} 
                   onClick = {() => this.remove(notification)}
                   className = "notification" 
                   key = {i}>
                   {notification}
              </div>
            )
          }
        )}

Когда вы делаете это, React не может правильно определить, когда ваш компонент удален. Например, удаляя элемент с индексом 0 и перемещая элемент с индексом 1 на его место, React будет думать, что элемент с ключом 0 был просто изменен, а не удален. Это может иметь различные побочные эффекты, такие как то, что вы видите.

Попробуйте использовать уникальный идентификатор, если он у вас есть, в противном случае используйте какой-либо увеличивающий ключ.

Чтобы быстро проверить это в коде, измените эти биты (конечно, я не рекомендую использовать ваше сообщение в качестве ключа):

add = () => {
    this.setState({notifications: [...this.state.notifications, Math.random().toString()]})
}

...

key = {notification}

Индексы массива следует использовать в качестве ключей компонентов только в крайнем случае.

Дилан спасибо за ответ. Однако если бы это было так, почему версия onClick для удаления элемента уведомления работала бы правильно? По сути, они делают одно и то же. onClick удалит элемент и вызовет пересчет ключа индекса, но в этом случае анимация продолжит функционировать.

mls3590712 05.04.2019 01:24

Трудно быть уверенным на 100%, потому что я не уверен на 100%, как React оптимизирует вещи с помощью ключей, но представьте что-то вроде: (Создание уведомления 0) -> (Создание уведомления 1) -> (Раннее отклонение уведомления 0) -> (Уведомление 1 наследует 0 onAnimationEnd, потому что React считает, что на самом деле это 0). Теперь представьте этот сценарий без ручного отключения: (0 срабатывает onAnimationEnd) -> (1 наследует 0 onAnimationend, но анимация уже закончилась, поэтому она никогда не сработает).

Dylan Walker 05.04.2019 01:31

Если бы ваши уведомления были более сложными по своему тексту, я гарантирую, что странные вещи начали бы происходить только с onClick(), они просто не заметны прямо сейчас, поскольку все ваши уведомления являются просто «тестовыми». Вы не должны верить мне на слово, хотя, дайте ему шанс!

Dylan Walker 05.04.2019 01:33

@ mls3590712 Вот, я придумал способ продемонстрировать. В вашем codepen создайте 1 уведомление, подождите ~ 4 секунды, затем создайте второе уведомление. Сразу после появления второго уведомления отклоните первое уведомление (то, что сверху). Один оставшийся немедленно пройдёт часть пути затухания и рано исчезнет.

Dylan Walker 05.04.2019 01:55

Я могу подтвердить, что ваше решение уникального ключа действительно решает проблему. Меня раздражает, что произошло такое странное поведение (ровно половина не работала, а другая половина работала). Спасибо за ваше понимание.

mls3590712 05.04.2019 02:10

Другие вопросы по теме