【问题标题】:React Native - Can't perform a React state update on an unmounted component, useEffect cleanup functionReact Native - 无法对未安装的组件执行 React 状态更新,使用Effect 清理功能
【发布时间】:2021-10-26 09:06:46
【问题描述】:

我知道已经有很多关于这方面的问题,但我已经尝试了所有我能找到的解决方案,但都没有奏效。我正在使用返回图像的 react-redux 执行 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.

组件中出现这种情况的代码是:

const image = useSelector((state) => state.images.image);

const dispatch = useDispatch();

const loadImage = useCallback(async () => {
  setError(null);
  setIsLoading(true);
  try {
    await dispatch(actions.fetchImage());
  } catch (err) {
    setError(err.message);
  }
  setIsLoading(false);
}, [dispatch]);

useEffect(() => {
  setIsLoading(true);
  loadImage().then(() => {
    setIsLoading(false);
  });
}, [setIsLoading, loadImage]);

我找到的最有希望的解决方案是这样,但这并没有摆脱错误:

useEffect(() => {
  let mounted = true;
  setIsLoading(true);
  loadImage().then(() => {
    if (mounted) {
      setIsLoading(false);
    }
  });

  return function cleanup() {
    mounted = false;
  };
}, [setIsLoading, loadImage]);

有什么想法吗?

【问题讨论】:

    标签: react-native react-redux react-hooks use-effect


    【解决方案1】:

    第一个观察结果是您只是取消了 useEffect 中的设置器,而不是 loadImage 中的设置器。

    众所周知,具有(状态)效果的异步函数很棘手。虽然您可能可以使用您提出的解决方案来解决它,但它会使您的代码更难阅读和维护。

    使用异步钩子

    我建议使用来自react-useuseAsync hook

    不幸的是,正如你所想,这个库与 React Native 不兼容。

    我冒昧地创建了一个基本的 useAsync 钩子,它应该对您的用例足够有用,并且应该在 React Native 中工作(我手头没有项目可以测试它):

    type UseAsyncResult<R> =
      | { loading: false; error: undefined; value: R }
      | { loading: boolean; error: any; value: undefined };
    
    function useAsync<T extends () => Promise<R>, R>(
      fn: T,
      deps: unknown[]
    ): UseAsyncResult<R> {
      const mounted = useRef(true);
    
      const [state, setState] = useState<UseAsyncResult<R>>({
        loading: true,
        error: undefined,
        value: undefined
      });
    
      useEffect(() => {
        return () => {
          mounted.current = false;
        };
      }, []);
    
      useEffect(() => {
        fn()
          .then((value) => {
            if (mounted.current) {
              setState({ loading: false, error: undefined, value });
            }
          })
          .catch((error) => {
            if (mounted.current) {
              setState({ loading: false, error, value: undefined });
            }
          });
      }, deps);
    
      return state;
    }
    

    它使用您在解决方案中提到的布尔值来查看组件是否仍然挂载,并且仅在挂载时执行状态更改。

    使用useAsync,您的代码将变为:

    const image = useSelector((state) => state.images.image);
    
    const dispatch = useDispatch();
    
    const { loading, error } = useAsync(() => dispatch(actions.fetchImage()), [dispatch]);
    

    如您所见,钩子为您维护loadingerror 值,无需执行任何操作,因此您可以专注于实际功能。如果您的 async 函数实际上返回了您想要使用的某个值,则该值将放置在名为 value 的属性中,如果 loading 为 false 且 error 没有值,您可以使用该属性。

    请记住,您传递给 useAsync 钩子的异步函数也不应该执行任何状态更新,否则您仍会收到相同的错误消息。

    【讨论】:

    • 当我这样做时,应用程序甚至没有启动,只是给我一个错误Can't find variable: document
    • 很遗憾,我猜是因为某种原因它与 React Native 不兼容?我一直在 Web 项目中使用它。我确实看到该库不再维护,这真是令人遗憾。
    • 我添加了 useAsync 钩子的实现,它应该也可以在 React Native 中使用
    • 非常感谢您解决了这个问题 - 我感谢您所做的努力!
    • 没问题,很高兴为您提供帮助!
    猜你喜欢
    • 2021-11-30
    • 2020-09-03
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    • 2019-09-11
    • 2021-08-07
    • 2020-06-14
    • 1970-01-01
    相关资源
    最近更新 更多