首页 > 解决方案 > 在 useEffect 中反应内存泄漏警告

问题描述

以下代码给了我泄漏内存警告:

警告:无法对未安装的组件执行 React 状态更新。这是一个空操作,但它表明您的应用程序中存在内存泄漏。要解决此问题,请在 useEffect 清理函数中取消所有订阅和异步任务。

const prepareMetaData = async () => {
    const languagesJson = localStorage.getItem('feedsLanguages');
    const platformModelsJson = localStorage.getItem('feedsPlatformModels');

    let languagesArr;
    let platformsArr;
    if (!ValidationUtils.isExists(languagesJson) || !ValidationUtils.isExists(platformModelsJson)) {
      const res = await fetch(buildApiUrl('feeds/meta-data', true, false), buildFetchRequest('GET'));
      await handleFailedFetchRequests(res);
      const metaData = await res.json();
      languagesArr = metaData.languages;
      platformsArr = metaData.platformModels;
      localStorage.setItem('feedsLanguages', JSON.stringify(metaData.languages));
      localStorage.setItem('feedsPlatformModels', JSON.stringify(metaData.platformModels));
    } else {
      languagesArr = JSON.parse(languagesJson);
      platformsArr = JSON.parse(platformModelsJson);
    }

    const langObj = TextUtils.setLanguages(languagesArr);
    setLanguagesDict(langObj);
    setPlatformModels(platformsArr);
  };

  const prepareData = async () => {
      prepareMetaData();
  };

  useEffect(() => {
    prepareData();
  }, []);

所以我尝试了以下方法:

let isCanceled = false;
const prepareMetaData = async () => {
    const languagesJson = localStorage.getItem('feedsLanguages');
    const platformModelsJson = localStorage.getItem('feedsPlatformModels');

    let languagesArr;
    let platformsArr;
    if (!ValidationUtils.isExists(languagesJson) || !ValidationUtils.isExists(platformModelsJson)) {
      const res = await fetch(buildApiUrl('feeds/meta-data', true, false), buildFetchRequest('GET'));
      await handleFailedFetchRequests(res);
      const metaData = await res.json();
      languagesArr = metaData.languages;
      platformsArr = metaData.platformModels;
      localStorage.setItem('feedsLanguages', JSON.stringify(metaData.languages));
      localStorage.setItem('feedsPlatformModels', JSON.stringify(metaData.platformModels));
    } else {
      languagesArr = JSON.parse(languagesJson);
      platformsArr = JSON.parse(platformModelsJson);
    }

    const langObj = TextUtils.setLanguages(languagesArr);
    if (!isCanceled) {
       setLanguagesDict(langObj);
       setPlatformModels(platformsArr);
    }
  };

  const prepareData = async () => {
      prepareMetaData();
  };

  useEffect(() => {
    prepareData();
    return () => {
      isCanceled = true;
    }
  }, []);

但它仍然无法正常工作。任何可行的解决方案?
谢谢。

标签: reactjsreact-hooks

解决方案


在异步函数中,您无法确定这两个setState调用是否在组件卸载之前运行。

setLanguagesDict(langObj);
// other stuff can happen here
setPlatformModels(platformsArr);

要解决此问题,使用您的解决方案,您需要在每次调用之前分别检查 isCancel 。

if (!isCanceled) {
    setLanguagesDict(langObj);
}
if (!isCanceled) {
    setPlatformModels(platformsArr);
}

要确认/复制问题(并证明解决方案有效),您可以检查此 REPL.it(特别是 App.js)文件。

需要明确的是 - 这是一个非常混乱的问题解决方案。我会考虑通过使用 refs 或 reducer 模式来避免重新渲染(显然取决于使用上下文)。

简而言之,我采用了 create react app 模板,并创建了一个非常愚蠢的 App.js,它在第一个 setState 后卸载,在效果运行后返回。在控制台打开的情况下运行它,您将看到消息出现。取消注释中间测试以查看它是否已修复。

import React from 'react';
import logo from './logo.svg';
import './App.css';

let internalCounter = 0;

let toggledIsActive = false;

const TogglingComponent = ({
    initialCounter,
    setCounter,
}) => {
    const [counterToShow, setInternalCounter] = React.useState(initialCounter);
    React.useEffect(() => {
        toggledIsActive = true;
        const waitAndUpdate = async () => {
            await new Promise(resolve => {
                setTimeout(() => resolve(), 1500);
            });
            internalCounter++;
            if (toggledIsActive) {
                setCounter(internalCounter);
            // Uncomment to fix state update
            // }
            // if (toggledIsActive) {
                setInternalCounter(internalCounter);
            }
        }

        const interval = setInterval(waitAndUpdate, 1500);
        return () => {
            toggledIsActive = false;
            clearInterval(interval);
        };
    });
    return <a
        className="App-link"
        href="https://reactjs.org"
        target="_blank"
        rel="noopener noreferrer"
    >
        Learn React {counterToShow}
    </a>;
}

function App() {
  const [counter, setCounter] = React.useState(0);

  const [lastCounter, setLastCounter] = React.useState(counter);
  React.useEffect(() => {
      setLastCounter(counter);
  }, [counter])

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code>!
        </p>
        {counter === lastCounter ? <TogglingComponent
        initialCounter={lastCounter}
        setCounter={setCounter}
        /> : null}
      </header>
    </div>
  );
}

推荐阅读