В этой статье я объясню некоторые вещи, которые вы можете сделать, чтобы предотвратить утечку памяти, которая происходит с асинхронным кодом, написанным в компоненте React, а также простой инструмент, который я создал, чтобы исправить это.
Для людей, которые не знают, что такое утечка памяти, вот объяснение: код, который мы пишем, обычно включает в себя создание различных типов объектов данных и их использование в течение некоторого времени. Когда объекты больше не используются, их следует удалить из памяти. Эта очистка выполняется либо кодом, написанным программистами в случае таких языков, как C или C++, либо сборщиками мусора в таких языках, как JavaScript или Java. Если не очистить неиспользуемые объекты данных, это приведет к увеличению использования памяти и, в конечном итоге, к сбою программного обеспечения. Это известно как утечка памяти. Сборщики мусора очищают неиспользуемые объекты данных, только если на них нет ссылок.
В React библиотека предупреждает нас следующим сообщением во время утечки памяти:
Вот что React говорит здесь: «смотрите, есть экземпляр этого компонента, который размонтирован и больше не требуется, но код, который вы написали, где-то содержит ссылки на этот экземпляр компонента. Интересно, откуда я это знаю? позвольте мне объяснить, ваш код вызывает метод setState этого размонтированного компонента, который, в свою очередь, запускает этот журнал».
Вот простой компонент, который приведет к утечке памяти.
Когда этот компонент монтируется, он вызывает сетевой вызов для получения задач. Допустим, этот сетевой вызов будет завершен через несколько секунд. До тех пор он будет содержать ссылки на обратные вызовы, переданные в блоках then, catch, а эти обратные вызовы, в свою очередь, удерживают ссылки на this.setState, которые также будут содержать ссылку на весь компонент.
Но в чем же дело! Ссылки не будут иметь значения после того, как обещание будет выполнено, верно?
Да, я согласен. Обещание будет удалено после того, как оно установится, а обратные вызовы будут выполнены, и их ссылки не будут иметь значения. Но о чем нам следует беспокоиться, так это о частоте таких вызовов и иерархии компонента, в котором они происходят. Предположим, что этот компонент является верхним узлом в иерархии и содержит большое количество дочерних компонентов. При утечке этого компонента вся секция дерева ниже этого узла не будет собирать мусор и будет занимать большой объем памяти. Представьте, что если пользователь постоянно переключается между такими компонентами, утечка памяти будет накапливаться и сделает приложение чрезвычайно медленным и вялым.
Как это исправить?
Есть три типа решений, которые мы можем применить к этой проблеме. Решений может быть больше, поделитесь в комментариях, если найдете.
1. Урегулируйте обещания, остановив их перед размонтированием.
2. Избегайте ссылок, избегая асинхронного кода в компоненте.
3. Удалите ссылки на обратные вызовы then, catch и finally.
Давайте изучим их:
1. Урегулируйте промисы, остановив их перед размонтированием:
Установленное обещание выполнит свои блоки «тогда, поймать и наконец», и обещание не будет иметь никаких ссылок на них, и компонент может быть удален сборщиком мусора.
Есть способы прервать текущие обещания, но все они имеют определенные ограничения использования, и одно решение не может быть применено для всех типов асинхронных операций.
Примеры, когда мы можем прервать асинхронные операции:
1. Для сервера вызовы: AbortController, Axios Cancellation
2. Обещания: Отмена обещания BlueBird
У приведенных выше решений есть небольшая проблема: они будут вызывать блок catch с ошибкой прерывания при прерывании, и это может произойти после размонтирования компонента, что снова дает то же предупреждение о реакции.
2. Избегайте ссылок, избегая асинхронного кода в компоненте:
Вы можете избежать этой проблемы, переместив состояние за пределы компонентов. В Redux, например, вы можете написать этот асинхронный код вне этого компонента, а затем вы можете просто отправить действие без каких-либо проблем с внутренними ссылками или значениями области действия из компонента, тем самым устраняя всю проблему.
3. Удалите ссылки на обратные вызовы then, catch и finally:
Что делать, если вы не хотите использовать избыточность, или вы не хотите перемещать состояние вашего компонента в хранилище, или вам не нужны какие-то тяжелые библиотеки для отмены промисов. Что, если есть простой способ очистить ссылки на обратные вызовы обещаний. Я создал инструмент для этого, вот фрагмент
Здесь мы вызываем метод fetchTodos с помощью утилиты AsyncAbort, которая возвращает метод для отмены обещания. Под отменой я подразумеваю, что ссылки, прикрепленные к обратным вызовам, будут удалены из вызова fetchTodos. Таким образом, обещание не будет остановлено, но прикрепленные обратные вызовы не будут вызываться, а также их ссылки будут очищены, предотвращая утечку.
Как это работает? Между промисом и обратными вызовами есть посредник, простой наблюдатель. Мы сделаем этот выпуск Observer ссылающимся на обратные вызовы при размонтировании компонента.
Вот подробное объяснение того, как AsyncAbort очищает ссылки на обратные вызовы:
Когда fetchTodos вызывается через AsyncAbort:
1. Он создает идентификатор для этого асинхронного вызова и сопоставляет фактические обратные вызовы, переданные с идентификатором в сопоставлении
2. Затем он вызовет fetchTodos и создаст прокси-обратные вызовы, которые будут вызывать наблюдателя промисов с { response, id }
, и эти обратные вызовы присоединяются к промису fetchTodos вместо фактические обратные вызовы.
3. Когда этот промис установится, он вызовет прокси-обратные вызовы, и они вызовут наблюдателя промисов с { response, id }
для выполнения фактических обратных вызовов. сопоставляются с заданным id .
4. Теперь, когда компонент размонтируется, вызов cancel
просто удалит фактические обратные вызовы из карты для этого id, а когда обещание установится, >фактический обратный вызов будет вызван. Тем самым предотвращая утечку.
Вы можете проверить код GitHub здесь: AsyncAbort
Вывод:
Мы узнали разные способы исправления утечки памяти в компоненте React из-за асинхронного кода. Утечка памяти происходит не только с асинхронным кодом в компоненте, она может произойти в любом месте кода, если мы не будем осторожны при удалении ссылок. Проверьте ссылки ниже в разделе «Ссылки», чтобы узнать больше.
Спасибо.
Использованная литература:
1. Отладка утечек памяти с помощью chrome dev tools
2. 4 типа утечек памяти в JavaScript и способы их устранения
3. Утечка прослушивателей событий — проблема React GitHub
4. Расследование утечки памяти React Discord