繁体   English   中英

UseEffect 挂钩错误,无法对未安装的组件执行 React state 更新

[英]UseEffect hook error, Can't perform a React state update on an unmounted component

当 appState 处于活动状态时,我试图在我的 useEffect 挂钩中设置 state,但是,我收到了警告。 当我的应用程序处于活动状态时,如何设置 state?

警告:无法对未安装的组件执行 React state 更新。 这是一个空操作,但它表明您的应用程序中存在 memory 泄漏。 要修复,请取消 useEffect 清理中的所有订阅和异步任务 function。

useEffect(() => {
    const subscription = AppState.addEventListener(
      "change",
      async (nextAppState) => {
        if (
          appState.current.match(/inactive|background/) &&
          nextAppState === "active"
        ) {
          setAppStateVisible(true); // set state to true
        }

        appState.current = nextAppState;
        setAppStateVisible(appState.current);
      }
    );

    return () => {
      subscription.remove();
    };
  }, []);

是的,这是 React 中的一个常见问题,目前还没有官方解决方案(AFAIK)。

你可以做这样的事情(它有效,但一半的 Inte.net 认为这是反模式):

useEffect(() => {
   let isMounted = true;
   const subscription = AppState.addEventListener("change", 
      async (nextAppState) => {
    if (appState.current.match(/inactive|background/) && nextAppState === "active" && isMounted) { 
        setAppStateVisible(true); // set state to true
    }

    appState.current = nextAppState;

    if(isMounted) {
        setAppStateVisible(appState.current);
    }

    });

  return () => {
    isMounted = false;
    subscription.remove();
  };
}, []);

基本上,这个想法是将您的 setAppStateVisible 调用包装在一个保护性检查中,检查该组件是否已安装 - 因为如果组件已卸载,您不应该更新该组件的 state(如消息所述)。

由于您在清理回调删除(取消)您的订阅,我猜您的async function 中可能有一个或多个await s(否则,为什么要将其设置为async function?),尽管您没有显示任何内容。 由于await s 意味着您的代码暂停然后稍后再次继续(当 promise 它正在await解决时),您需要考虑到您的组件在此期间被卸载的可能性。

理想情况下, subscription可以让您检查它是否已被删除(如AbortSignalaborted标志)。 不过,我找不到它的任何文档;AppState.addEventListener说它是一个EventSubscription ,但没有提供链接,而且我找不到EventSubscription的任何文档。

如果它有一个,你想使用它。 否则,除了删除订阅的代码之外,您还可以添加一个简单的标志:

useEffect(() => {
    let cancelled = false;                              // ***
    const subscription = AppState.addEventListener(
        "change",
        async (nextAppState) => {
            if (cancelled) {                            //
                return;                                 // ***
            }                                           //
            if (
                appState.current.match(/inactive|background/) &&
                nextAppState === "active"
            ) {
                setAppStateVisible(true); // set state to true
            }

            await something();                          // Example of an `await`
            if (cancelled) {                            //
                return;                                 // ***
            }                                           //
    
            appState.current = nextAppState;
            setAppStateVisible(appState.current);
        }
    );

    return () => {
        cancelled = true;                               // ***
        subscription.remove();
    };
}, []);

如果你没有任何await ,那么 React Native 似乎有一些竞争条件,即使你已经对它进行了remove ,它仍然可能调用你的订阅回调,这不太理想。 上面也会处理这个问题。

单独使用这样cancelled标志通常被认为是一种反模式(取消订阅比忽略调用更好),但这不是我们上面所做的。 我们正在取消订阅处理取消订阅后代码被调用或继续的可能性,这很好。

如果你在EventSubscription上找不到标志,你可以考虑给自己一个包装器,这样你就可以轻松地重用它:

function subscribeAppState(fn) {
    const subscription = AppState.subscribe(fn);
    return {
        remove() {
            subscription.remove();
            this.removed = true;
        }
    };
}

然后:

useEffect(() => {
    const sub = subscribeAppState(
        "change",
        async (nextAppState) => {
            if (sub.removed) {                          //
                return;                                 // ***
            }                                           //
            if (
                appState.current.match(/inactive|background/) &&
                nextAppState === "active"
            ) {
                setAppStateVisible(true); // set state to true
            }

            await something();                          // Example of an `await`
            if (sub.removed) {                          //
                return;                                 // ***
            }                                           //
    
            appState.current = nextAppState;
            setAppStateVisible(appState.current);
        }
    );

    return () => {
        sub.remove();
    };
}, []);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM