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

Reactjs 在useEffect之外设置未安装组件的状态,reactjs,asynchronous,react-hooks,Reactjs,Asynchronous,React Hooks,在异步clickHandler中卸载组件后调用setState时,控制台中会出现警告: Warning: Can't perform a React state update on an unmounted component. 看 函数应用程序(){ const[show,setShow]=useState(true) 返回{show&&setShow(false)} /> } } 函数按钮({text,clickHandler}){ 常量[状态,设置状态]=使用状态(文本); const

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

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

函数应用程序(){
const[show,setShow]=useState(true)
返回{show&&setShow(false)}
/> }
}
函数按钮({text,clickHandler}){
常量[状态,设置状态]=使用状态(文本);
const handleClick=async()=>{
等待clickHandler();
设置状态(“我被点击”);
};
返回{state};
}
单击
单击以删除
按钮将卸载该按钮,然后更新状态

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

我不认为使用useEffect可以很好地避免这种情况

本例中有两种可能的解决方法。 可以使用
useRef
检查组件是否已卸载(如中建议的)。这感觉不太像


另一种解决方法使用建议的
useffect
guard变量(like)。但是要使clickHandler退出
useffect
,clickHandler将存储在一个状态中。然而,要使一个函数进入一个状态,它需要包装在另一个函数中,因为
useState
setState
函数将调用作为参数给出的函数。

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

我们定义了两个自定义挂钩,
useIsMountedRef
useStateWithMountCheck
。您可以像
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];
}

我的建议是将组件转换为React组件类的扩展,并使用
componentWillUnmount
。但我的实际建议是找出你为什么会有这种副作用并解决它,而不是仅仅修补这个问题。你能在你的问题中添加相关的代码吗?codesandbox链接对我不起作用。@palortoff你能解释一下为什么你一般反对使用useRef吗?据我所知,类样式组件中的传统生命周期方法依赖于类似的构造。你不觉得useStateWithMountCheck感觉像“反应一样”?
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];
}