Я делаю простую службу уведомлений в 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



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы используете индекс массива в качестве ключа компонента:
{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}
Индексы массива следует использовать в качестве ключей компонентов только в крайнем случае.
Трудно быть уверенным на 100%, потому что я не уверен на 100%, как React оптимизирует вещи с помощью ключей, но представьте что-то вроде: (Создание уведомления 0) -> (Создание уведомления 1) -> (Раннее отклонение уведомления 0) -> (Уведомление 1 наследует 0 onAnimationEnd, потому что React считает, что на самом деле это 0). Теперь представьте этот сценарий без ручного отключения: (0 срабатывает onAnimationEnd) -> (1 наследует 0 onAnimationend, но анимация уже закончилась, поэтому она никогда не сработает).
Если бы ваши уведомления были более сложными по своему тексту, я гарантирую, что странные вещи начали бы происходить только с onClick(), они просто не заметны прямо сейчас, поскольку все ваши уведомления являются просто «тестовыми». Вы не должны верить мне на слово, хотя, дайте ему шанс!
@ mls3590712 Вот, я придумал способ продемонстрировать. В вашем codepen создайте 1 уведомление, подождите ~ 4 секунды, затем создайте второе уведомление. Сразу после появления второго уведомления отклоните первое уведомление (то, что сверху). Один оставшийся немедленно пройдёт часть пути затухания и рано исчезнет.
Я могу подтвердить, что ваше решение уникального ключа действительно решает проблему. Меня раздражает, что произошло такое странное поведение (ровно половина не работала, а другая половина работала). Спасибо за ваше понимание.
Дилан спасибо за ответ. Однако если бы это было так, почему версия onClick для удаления элемента уведомления работала бы правильно? По сути, они делают одно и то же. onClick удалит элемент и вызовет пересчет ключа индекса, но в этом случае анимация продолжит функционировать.