如何在useEffect钩子反应中停止内存泄漏

新手上路,请多包涵

我正在使用效果挂钩从服务器获取数据,这些数据被传递到反应表,在那里我使用相同的 api 调用从服务器加载下一组数据。当应用程序被加载时,我收到如下警告

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

效果挂钩:

 useEffect(() => {
setPageLoading(true);
props
  .dispatch(fetchCourses())
  .then(() => {
    setPageLoading(false);
  })
  .catch((error: string) => {
    toast.error(error);
    setPageLoading(false);
  });
}, []);

反应表页面:

 <ReactTable
  className="-striped -highlight"
  columns={columns}
  data={coursesData}
  defaultPage={currentPage}
  defaultPageSize={courses.perPage}
  loading={isLoading}
  manual={true}
  onFetchData={setFilter}
/>

设置过滤功能:

 const setFilter = (pagination: any) => {
  props.dispatch(updateCoursePageSize(pagination.pageSize));
  props.dispatch(updateCourseCurrentPage(pagination.page + 1));
  setCurrentPage(pagination.page);
  setPerPage(pagination.pageSize);
  setLoading(true);
  props.dispatch(fetchCourses()).then(() => {
    setLoading(false);
  });
};

有谁知道如何清理反应中的钩子

原文由 Nidhin Kumar 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 846
2 个回答

更新(2022 年 6 月):

React 18 已删除此警告消息,并且可能不再需要摆脱它的变通方法。他们删除它的部分原因是它总是有点误导。它说你有内存泄漏,但很多时候你没有。

问题中的代码(实际上是导致此警告的大多数代码)在卸载组件后运行了有限的时间,然后设置状态,然后完成运行。由于运行完毕,javascript 可以在其闭包中释放变量,因此通常不会出现泄漏。

如果您正在设置无限期持续的持久订阅,则会发生内存泄漏。例如,也许您设置了一个 websocket 并收听消息,但您从不拆除该 websocket。这些情况确实需要修复(通过向 useEffect 提供清理功能)但它们并不常见。

react 18 删除警告的另一个原因是他们正在研究组件在卸载后保持其状态的能力。一旦该功能生效,卸载后设置状态将是一件非常有效的事情。

原始答案(2019 年 9 月):

使用 useEffect 您可以返回一个将在清理时运行的函数。所以在你的情况下,你会想要这样的东西:

 useEffect(() => {
  let unmounted = false;

  setPageLoading(true);

  props
    .dispatch(fetchCourses())
    .then(() => {
      if (!unmounted) {
        setPageLoading(false);
      }
    })
    .catch((error: string) => {
      if (!unmounted) {
        toast.error(error);
        setPageLoading(false);
      }
    });

  return () => { unmounted = true };
}, []);

编辑:如果您需要在 useEffect 之外启动一个调用,那么它仍然需要检查一个未安装的变量以判断它是否应该跳过对 setState 的调用。未安装的变量将由 useEffect 设置,但现在您需要克服一些障碍才能使该变量在效果之外可访问。

 const Example = (props) => {
  const unmounted = useRef(false);
  useEffect(() => {
    return () => { unmounted.current = true }
  }, []);

  const setFilter = () => {
    // ...
    props.dispatch(fetchCourses()).then(() => {
      if (!unmounted.current) {
        setLoading(false);
      }
    })
  }

  // ...
  return (
    <ReactTable onFetchData={setFilter} /* other props omitted */ />
  );
}

原文由 Nicholas Tower 发布,翻译遵循 CC BY-SA 4.0 许可协议

您可以像这样创建一个自定义挂钩:

 import * as React from 'react';

export default function useStateWhenMounted<T>(initialValue: T) {
  const [state, setState] = React.useState(initialValue);
  const isMounted = React.useRef(true);
  React.useEffect(() => {
    return () => {
      isMounted.current = false;
    };
  }, []);

  const setNewState = React.useCallback((value) => {
    if (isMounted.current) {
      setState(value);
    }
  }, []);

  return [state, setNewState];
}

原文由 Ilan Roitlender 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进