Утечка памяти в отсоединенном узле DOM в React - PullRequest
1 голос
/ 13 февраля 2020

РЕДАКТИРОВАТЬ Похоже, что это связано с проблемой в React и будет исправлено в будущем выпуске. https://github.com/facebook/react/issues/18066


Учитывая таблицу в реакции, которая отображает данные из API, которые могут быть обновлены с совершенно новой информацией, я наблюдал утечку отсоединенного узла DOM (наблюдайте зеленые цифры ):

Gif of memory leak

Вот код, выполненный в формате GIF (код, указанный ниже для потомков). Чтобы увидеть утечку, go на полную страницу , откройте chrome dev инструменты, откройте вкладку «Performance Monitor» и быстро нажмите кнопку «Regen», как показано на рисунке.

В этом коде и поле , где узлы не генерируются в al oop, утечка не происходит.

Единственная разница - {rows} массив в jsx. Смущающая часть заключается в том, что {rows} не является глобальной переменной, поэтому я не вижу, как это помешало бы старым узлам быть G C 'd.

Почему используется локальная переменная rows приводит к утечке отсоединенного узла DOM?

Примечание. Кажется, что узлы DOM устанавливаются на 21 000, но в любом случае их не должно быть так много, как вы можете видеть, они начинаются с 7 000 после первого поколения таблицы. В моем приложении из реальной жизни эти отсоединенные узлы сохраняются даже благодаря навигации (с реагирующим маршрутизатором), что заставляет меня поверить, что это фактическая утечка, а не просто узлы, ожидающие получения G C 'd.


Полный код, имитирующий утечку:

import React, { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <div className="App">
      <button onClick={() => setCount(prev => prev + 1)}>Regen</button>
      <FTable count={count} />
    </div>
  );
}

function Cell() {
  const num = Math.floor(Math.random() * 100);
  return <td>{num}</td>;
}
function FTable(props) {
  const { count } = props;
  const rows = [];
  if (count > 0) {
    for (let i = 0; i < 1000; i++) {
      rows.push(
        // Use a different key for each time the
        // table is regenerated to simulate a new API
        // call bringing in new data
        <tr key={`${i} ${count}`}>
          <Cell row={i} />
          <Cell row={i} />
          <Cell row={i} />
        </tr>
      );
    }
  }
  return (
    <div>
      <table>
        <tbody>{rows}</tbody>
      </table>
    </div>
  );
}

1 Ответ

3 голосов
/ 18 февраля 2020

Сначала я подумал, что это ошибка в Hooks API. Потому что если вы замените <FTable count={count} /> на <FTable count={1} />, ошибка исчезнет. Но это не решение.

Существует проблема о неожиданном поведении с крючками. Но в этом случае вместо узлов DOM размер кучи JS увеличивается.

Тогда я подумал: «Хорошо, я попробую этот случай с компонентом класса», и я сделал эту демонстрацию . Та же проблема все еще здесь. Хорошо, что если эта проблема была введена вместе с Хуками в версии 16.3? Но нет. Та же самая проблема существует в 16.0 .

И тогда я понимаю. Ключевой вопрос: что общего между всеми этими случаями? Ключ!

Документация гласит: :

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

Оказалось, что React недостаточно умён для G C старых узлов, если ключ уникален на каждом рендере (ну, в данном случае) . Вот почему, если вы используете <tr key={i}>, тогда все в порядке, потому что React "переписывает" эти узлы, а когда вы используете ${i * count} или ${i} ${count} или ", то, что уникально на каждом render ", тогда узлы будут в памяти. Через некоторое время старые узлы будут заменены новыми, но я думаю, что это поведение браузера, а не React. Но я не эксперт по реагированию и не знаю, где и как именно это происходит.

На данный момент вы можете создать проблему на GitHub и знать об этой проблеме.

...