Реагировать: прекратить проверять, смонтирован ли ваш компонент

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

Обновление 10.08.2021: Между написанием и публикацией этого сообщения в блоге предупреждение setState было удалено из базы кода React. Это изменение будет включено в следующую версию React. Эта статья остается правильной и подчеркивает, почему было внесено это изменение. Цитата Дэна Абрамова (источник):

Хотя сообщение вроде правильное. Мы удаляем это предупреждение , потому что люди обходят его с помощью isMounted, что противоречит цели. Так что лучше не предупреждать. Но правильное решение - отмена. Или игнорируйте, если вам лень отменять.

Исходная история. Как и в случае с любым другим приложением React, нам приходится иметь дело с асинхронными обновлениями состояния в Doctolib, которые иногда приводят к «предупреждению setState». Они еще более заметны благодаря нашей стратегии тщательного тестирования, включающей более 30 000 сквозных тестов, которые не пройдут при любом виде предупреждения. Вот как мы преодолели утомительный процесс избавления от всех наших предупреждений setState, не проверяя, смонтированы ли наши компоненты.

Немного контекста

Как и большинство разработчиков React, вы, вероятно, хотя бы раз в жизни сталкивались с «предупреждением setState»:

Предупреждение: невозможно выполнить обновление состояния React для отключенного компонента. Это не работает, но указывает на утечку памяти в вашем приложении. Чтобы исправить это, отмените все подписки и асинхронные задачи в функции очистки useEffect.

Но задумывались ли вы когда-нибудь о том, что происходит на самом деле и почему вы это получаете? Если это так, должно быть, по какой-то причине, верно?

Вот упрощенный график того, что происходит и почему вы получаете это предупреждение:

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

Представляем антипаттерн

Если, как и я, вашей первой реакцией было поискать в Интернете решение этого раздражающего предупреждения, вы, вероятно, нашли ответ, похожий на этот, в его простейшей версии, используя механизм очистки useEffect:

Можно даже извлечь это в ловушку для дальнейшего использования, как вы можете найти в нескольких принятых ответах StackOverflow:

Плохие новости: это абсолютно не лучший способ справиться с этим предупреждением, и большинство ресурсов, которые вы можете найти по этой теме в Интернете, неверны.

Этот код ничего не делает, кроме скрытия предупреждения. React уже выполняет внутреннюю проверку и не обновляет компонент, который был отключен (вот почему у вас есть предупреждение). Это только перемещает проверку, чтобы подавить предупреждение:

Почему это плохо?

При использовании isMounted или подобных механизмов ссылка на размонтированный компонент сохраняется без причины. Это источник утечки памяти.

Кроме того, ваше приложение продолжит выполнять запросы и связанные с ними обратные вызовы. Это не даст никакого эффекта: результат всей этой обработки в конечном итоге будет проигнорирован.

Наконец, это просто очень сложный способ скрыть предупреждение. Если ваша единственная цель - игнорировать это, у вас есть более эффективные способы сделать это.

Вы можете найти больше информации об этом в статье 2015 года в техническом блоге React. Примечание: даже несмотря на то, что в этой статье содержится предложение о повторной реализации, оно дается только как переходный этап, а не как долгосрочное решение. Кроме того, AbortController интерфейса в то время не существовало.

И если вы еще не уверены… Чтобы представить предупреждение, React уже использует проверку того, что компонент смонтирован. Если бы это было правильным способом, они бы не предупредили вас об этом.

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

Что мне делать вместо этого?

Отмените ваши запросы! Для простых случаев использования, которые используют Fetch API, вы должны использовать AbortController API для отмены ваших запросов.

Я могу понять, если ваша первая реакция связана с многословием этого фрагмента кода. Но то, что происходит здесь, является ключом к правильному подавлению этого предупреждения: в строке 13 при вызове abort запрос перестает выполняться. Мало того, что его результат игнорируется, then в строке 4 никогда не будет вызван. Вместо этого код будет проходить через блок catch, в котором мы можем поймать AbortError и правильно избавиться от кода. Никаких setState не задействовано.

Чтобы узнать больше об AbortController и обсуждении WHATWG о том, как прервать выборочный запрос, эта отличная статья из блога Google Developers дает представление о текущем состоянии реализации и ее истории.

А как насчет асинхронных операций без выборки?

Когда я представил это на нашей встрече Tech Time, возникли некоторые опасения, поскольку часть нашей кодовой базы взаимодействует с пользовательскими интерфейсами API для настольных компьютеров или с Tanker SDK, который мы используем для выполнения сквозного шифрования большей части наших данных. .

Для этого вы должны реализовать механизм отмены ваших асинхронных операций. Однако вам решать, насколько AbortController API очень прост и адаптивен и доступен практически везде (в большинстве современных браузеров и в Node.js), поэтому это будет рекомендуемый способ решения этой проблемы. проблема в ваших пользовательских интерфейсах. Например, это то, что AWS сделала со своим Javascript SDK, а также с Azure.

Некоторые другие веб-API также используют аналогичные реализации, что и AbortController, например IndexedDB, который реализует метод abort для транзакции: transaction.abort() и FileReader API, который предоставляет метод instanceOfFileReader.abort().

Заключение

При работе с Fetch API отмена запросов упрощается с помощью AbortController. Это решает 90% случаев «предупреждения setState». И становится легко абстрагироваться от логики AbortController, связанной с жизненным циклом React, в настраиваемом хуке. Мы используем этот хук в Doctolib:

Так что помните: если вы обнаружите, что проверяете, что ваш компонент смонтирован, уже слишком поздно.

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

И если вы хотите присоединиться к нам в масштабировании веб-сайта с высокой посещаемостью и преобразовании системы здравоохранения, мы нанимаем талантливых разработчиков для развития нашей технической и производственной команды во Франции и Германии, не стесняйтесь взглянуть на открытые вакансии ».