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

Рассмотрим часто используемый пример:

import { useState, useEffect } from "react";
​
export const UserList = () => {
  const [users, setUsers] = useState(null);
​
  useEffect(() => {
    const getUsers = async () => {
      const usersResponse = await fetch("https://dummy-api.com/api/v1/users");
      setUsers(await usersResponse.json());
    };
    getUsers();
  }, []);
​
  return (
    <>
       ...my-jsx...
    </>
  );
};

Приведенный выше код иллюстрирует простой компонент React, извлекающий данные и устанавливающий результат в состояние. Но что произойдет, если пользователь перейдет из компонента до того, как API ответит? Ответ заключается в том, что setState вызывается после размонтирования компонента. Это означает, что мы устанавливаем состояние для несмонтированного компонента. React ‹= 17 выдавал ошибку в консоли, как показано на изображении ниже.

Правда ли, что этот случай вызывает утечку памяти, как нам сообщается в сообщении? Чтобы понять это, нам нужно некоторое базовое понимание управления памятью.

Управление памятью

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

Концепция управления памятью состоит из трех жизненных циклов, практически одинаковых независимо от языка.

  1. Выделение необходимой памяти
  2. Использовать выделенную память
  3. Освобождать выделенную память, когда она не нужна

В этой статье я не буду подробно объяснять эти жизненные циклы. Но нам нужно понять, что означает освобождение выделенной памяти и как это делается в JavaScript.

Освобождение выделенной памяти

Освобождение выделенной памяти означает удаление из памяти данных, которые больше не нужны. Освобождение выделенной памяти в JavaScript осуществляется сборщиком мусора (GC), поэтому разработчикам не нужно вручную освобождать память.

Освобождение выделенной памяти по ссылке — основная концепция сборщиков мусора. Концепция ссылки касается того, есть ли у объекта какие-либо другие объекты, ссылающиеся на него, или нет. Если у объекта нет других объектов, ссылающихся/указывающих на него, он может быть собран для GC, чтобы освободить выделенную память.

Вернуться к примеру с React

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

Итак, что произойдет, если setState вызывается для несмонтированного компонента? Поскольку вызов API является промисом, который разрешается после того, как не будет больше непрерывных ссылок на объект состояния после его установки.

Мы узнали, что объекты, на которые не ссылаются другие объекты, могут быть собраны для GC. Поскольку у нашего пользовательского состояния нет другого объекта, который на него ссылается, оно будет собрано и освобождено из выделенной памяти. Следовательно, сообщение об утечке памяти, которое мы видели, не соответствует действительности в этом конкретном сценарии.

Если бы наш компонент подписывался на соединение WebSocket вместо HTTP-запроса Promise, произошла бы утечка памяти. Произойдет утечка памяти, потому что подписанное соединение WebSocket будет продолжать прослушивать события. Следовательно, будет другой объект, который ссылается на состояние. Как мы узнали, объекты, на которые ссылается другой объект, не могут быть собраны сборщиком мусора.

Заключение

Сообщение от React было ложным срабатыванием и сбило разработчиков с толку, полагая, что они получили утечку памяти, выполняя регулярные HTTP-запросы к серверной части, когда компонент был размонтирован до ответа на HTTP-запрос.

React объединил запрос на извлечение, чтобы удалить сообщение об утечке памяти. Пожалуйста, не стесняйтесь посмотреть запрос на включение на Github, https://github.com/facebook/react/pull/22114 для более подробной информации.

Спасибо за прочтение! 🙌