在 useEffect 之外的未安装组件上设置状态

Set state on unmounted component outside of useEffect

在异步 clickHandler 中卸载组件后调用 setState 会导致控制台出现警告:

Warning: Can't perform a React state update on an unmounted component.

Codesandbox Example

function App() {
  const [show, setShow] = useState(true)
  return {show && <Button
          text="Click to remove"
          clickHandler={() => setShow(false)}
        /> }
}
function Button({ text, clickHandler }) {
  const [state, setState] = useState(text);

  const handleClick = async () => {
    await clickHandler();
    setState("I was clicked");
  };

  return <button onClick={handleClick}> {state}</button>;
}

单击 click to remove 按钮将卸载按钮,然后更新状态。

目标是在 异步调用后 设置按钮组件的状态。此异步调用可以但不必触发 Button 被卸载。我正在寻找一种在异步调用后选择退出 setState 的方法。

我看不出如何使用 useEffect 很好地避免这种情况。

示例中有两种可能的解决方法。 一个使用 useRef 检查组件是否已卸载(如 #14369 (comment) 中推荐的那样)。这感觉不太像反应。

另一种解决方法使用推荐的 useEffect 保护变量(如 #14369 (comment))。但是为了让 clickHandler 脱离 useEffect,clickHandler 被存储在一个状态中。但是,要使函数进入某个状态,它需要包装在另一个函数中,因为 useStatesetState 函数将调用作为参数给定的函数。

这是一个很好的问题,我不知道为什么它没有得到任何关注。我相信我找到了一个很好的解决方案,如果您认为这可以解决问题,请告诉我。该解决方案基于这样一个事实,即我们可以使用 useRef() 而无需在任何地方使用 ref 属性。

我们定义了两个自定义挂钩,useIsMountedRefuseStateWithMountCheck。您可以像 useState 挂钩一样使用后者,如果不再安装组件,它将忽略任何要求的状态更改。

function useIsMountedRef(){
  const ref = useRef(null);
  ref.current = true;

  useEffect(() => {
    return () => {
      ref.current = false;
    }
  });

  return ref;
}

function useStateWithMountCheck(...args){
  const isMountedRef = useIsMountedRef();
  const [state, originalSetState] = useState(...args);

  const setState = (...args) => {
    if (isMountedRef.current) {
      originalSetState(...args);
    }
  }

  return [state, setState];
}

Checkout the Sandbox.