【问题标题】:How to stop memory leak in useEffect hook react如何在useEffect钩子反应中停止内存泄漏
【发布时间】:2020-01-22 01:57:29
【问题描述】:

我正在使用效果挂钩从服务器获取数据,这些数据被传递到反应表,我使用相同的 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);
  });
};

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

【问题讨论】:

    标签: reactjs react-table use-effect


    【解决方案1】:

    使用 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 */ />
      );
    }
    

    【讨论】:

    • 但是如果在没有 useEffect 的情况下再次执行相同的 API 调用会怎样。例如,如果 API 调用完成是 useEffect 并在函数中再次执行。
    • @Nicholas Tower 如果您看到 setFilter 函数,则会调用相同的调度调用 fetchCourses()。如果两个调用同时发生,我会收到内存泄漏警告。如果我在 setFilter 函数中隐藏调用,则没有内存泄漏警告
    【解决方案2】:

    其他答案当然有效,我只是想分享一个我想出的解决方案。 我构建了这个hook,它的工作原理与 React 的 useState 类似,但只有在安装组件时才会设置状态。我发现它更优雅,因为您不必在组件中使用 isMounted 变量!

    安装:

    npm install use-state-if-mounted
    

    用法:

    const [count, setCount] = useStateIfMounted(0);
    

    您可以在钩子的npm page 上找到更多高级文档。

    【讨论】:

    • 这个 repo 的作者说这不是一个可行的解决方案(只是隐藏了警告信息)。我也很沮丧,但仅供大家参考。
    【解决方案3】:

    内存泄漏发生,当一个不必要的并且应该从内存中清除的东西因为其他东西仍然持有它而被保留时。在 React 组件的情况下,组件中进行的异步调用可能会保存 setState 或其他引用的引用,并将保存它们直到调用完成。 您看到的警告来自 React,它说某些东西仍在保持和设置组件实例的状态,该组件实例在组件卸载时很久以前就从树中删除了。现在使用标志来不设置状态只会删除警告但不会删除内存泄漏,即使使用 Abort 控制器也是如此。为了避免这种情况,您可以使用状态管理工具来帮助调度一个操作,该操作将在组件外部进行处理,而无需保存组件的任何内存引用,例如 redux。如果您不使用此类工具,那么您应该找到一种方法来清除组件卸载时传递给异步调用的回调(然后,catch,finally 块)。在下面的 sn-p 中,我正在做同样的事情,分离对传递给异步调用的方法的引用以避免内存泄漏。 这里的 Event Emitter 是一个 Observer,你可以创建一个或者使用一些包。

    const PromiseObserver = new EventEmitter();
    
    class AsyncAbort {
      constructor() {
        this.id = `async_${getRandomString(10)}`;
        this.asyncFun = null;
        this.asyncFunParams = [];
        this.thenBlock = null;
        this.catchBlock = null;
        this.finallyBlock = null;
      }
    
      addCall(asyncFun, params) {
        this.asyncFun = asyncFun;
        this.asyncFunParams = params;
        return this;
      }
    
      addThen(callback) {
        this.thenBlock = callback;
        return this;
      }
    
      addCatch(callback) {
        this.catchBlock = callback;
        return this;
      }
    
      addFinally(callback) {
        this.finallyBlock = callback;
        return this;
      }
    
      call() {
        const callback = ({ type, value }) => {
          switch (type) {
            case "then":
              if (this.thenBlock) this.thenBlock(value);
              break;
            case "catch":
              if (this.catchBlock) this.catchBlock(value);
              break;
            case "finally":
              if (this.finallyBlock) this.finallyBlock(value);
              break;
            default:
          }
        };
        PromiseObserver.addListener(this.id, callback);
        const cancel = () => {
          PromiseObserver.removeAllListeners(this.id);
        };
        this.asyncFun(...this.asyncFunParams)
          .then((resp) => {
            PromiseObserver.emit(this.id, { type: "then", value: resp });
          })
          .catch((error) => {
            PromiseObserver.emit(this.id, { type: "catch", value: error });
          })
          .finally(() => {
            PromiseObserver.emit(this.id, { type: "finally" });
            PromiseObserver.removeAllListeners(this.id);
          });
        return cancel;
      }
    }
    
    

    在 useEffect 钩子里你可以做

    React.useEffect(() => {
        const abort = new AsyncAbort()
          .addCall(simulateSlowNetworkRequest, [])
          .addThen((resp) => {
            setText("done!");
          })
          .addCatch((error) => {
            console.log(error);
          })
          .call();
        return () => {
          abort();
        };
      }, [setText]);
    

    我从here 分叉了某人的代码以使用上述逻辑,您可以在下面的链接中查看它的实际操作 link

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-27
      • 2021-10-14
      • 1970-01-01
      • 2021-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-13
      相关资源
      最近更新 更多