Reactjs 使用对话框中的有状态按钮或材质UI中的警报来响应内存泄漏警告

Reactjs 使用对话框中的有状态按钮或材质UI中的警报来响应内存泄漏警告,reactjs,material-ui,state,react-hooks,jss,Reactjs,Material Ui,State,React Hooks,Jss,我正在为组件库使用材质UI,并注意到当我单击对话框或警报(两个组件都管理打开/关闭状态)中的按钮时,会收到内存泄漏警告。我不确定如何解决这个问题。按钮组件在单击时使用状态创建活动类,它使用setTimeoutonClick使按钮单击在UI中更可见/更持久 这是按钮组件: function Button({ classes, className, onClick, ...props }) { let [active, setActive] = useState(false);

我正在为组件库使用材质UI,并注意到当我单击对话框或警报(两个组件都管理打开/关闭状态)中的按钮时,会收到内存泄漏警告。我不确定如何解决这个问题。按钮组件在单击时使用状态创建活动类,它使用
setTimeout
onClick使按钮单击在UI中更可见/更持久

这是按钮组件:

function Button({
  classes,
  className,
  onClick,
  ...props
}) {
  let [active, setActive] = useState(false);

  let handleClick = e => {
    e.persist();
    setActive(true);
    setTimeout(() => {
      setActive(false);
    }, 250);
  if (typeof onClick === "function") onClick(e);
  };

  return (
    <MuiButton
      variant={finalVariant(variant)}
      className={`${active ? "Mui-active" : ""} ${className}`}
      classes={buttonClasses}
      onClick={handleClick}
      {...props}
    />
  );
}

let containedStyle = color => ({
  "&:active": {
    backgroundColor: color.dark
  },
  "&.Mui-active": {
    backgroundColor: color.dark
  }
});
index.js:1437 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.

我已尝试使用警告提示的
useffect
清除活动状态,但没有成功。下面是一个演示,演示了当我使用一个用MUI构建的自定义按钮时会发生什么情况,该按钮在对话框或警报中使用时使用挂钩来管理状态,这是因为您的
handleClick
函数使用
setTimeout

  let handleClick = e => {
    e.persist();
    setActive(true);
    setTimeout(() => {
      setActive(false);
    }, 250);
    if (typeof onClick === "function") onClick(e);
  };
以更新状态

调用
onClick
时,父组件正在卸载该组件,但仍有一个订阅(您的超时)保持活动状态

如果这是一次过的活动,比如在本例中,这其实没什么大不了的。这是警告,不是错误。此警告的主要目的是让您知道在卸载某些内容后,您是否将订阅或引用保留很长时间

有一些解决方法可以通过在组件卸载时设置标志来消除警告,如果设置了标志,则不更新状态,但这并不能真正解决卸载后保留对组件的引用的问题

解决此问题的更好方法是使用
React.useRef()
保留对超时的引用,然后在
useffect()
中清除它,如下所示:

功能按钮({
班级,
类名,
onClick,
…道具
}) {
let[active,setActive]=useState(false);
+const timeout=React.useRef(未定义);
+React.useffect(()=>{
+return()=>{
+if(timeout.current!==未定义){
+clearTimeout(timeout.current);
+     }
+   }
+ }, []);
让handleClick=e=>{
e、 坚持();
setActive(真);
-设置超时(()=>{
+timeout.current=setTimeout(()=>{
setActive(假);
}, 250);
if(typeof onClick==“function”)onClick(e);
}; 
返回(
);
}
可以将其封装在钩子中,如下所示:

  function useSafeTimeout() {
    const timeouts = React.useRef([])
    React.useEffect(() => {
      return () => {
        timeouts.forEach(timeout => {
          clearTimeout(timeout)
        })
      }
    }, [])

    return React.useCallback((fn, ms, ...args) => {
      const cancel = setTimeout(fn, ms, ...args)
      timeouts.current.push(cancel)
    }, [])
  }
并以这种方式使用:

功能按钮({
班级,
类名,
onClick,
…道具
}) {
let[active,setActive]=useState(false);
+const setTimeout=useSafetTimeout();
让handleClick=e=>{
e、 坚持();
setActive(真);
设置超时(()=>{
setActive(假);
}, 250);
if(typeof onClick==“function”)onClick(e);
}; 
返回(
);
}

以下是我的问题解决方案:

function Button({
  classes,
  className,
  onClick,
  ...props
}) {
  let [active, setActive] = useState(false);
  let timeoutIds = useRef([]);

    let registerTimeout = (f, ms) => {
    let timeoutId = setTimeout(f, ms);
    timeoutIds.current.push(timeoutId);
  };

  let handleClick = e => {
    e.persist();
    setActive(true);
    if (typeof onClick === "function") onClick(e);
  };

  let cleanup = () => {
    timeoutIds.current.forEach(clearTimeout);
  };

  useEffect(() => {
    if (active === true) {
      registerTimeout(() => setActive(false), 250);
    }
    return cleanup;
  }, [active]);

  return (
    <MuiButton
      variant={finalVariant(variant)}
      className={`${active ? "Mui-active" : ""} ${className}`}
      classes={buttonClasses}
      onClick={handleClick}
      {...props}
    />
  );
}
功能按钮({
班级,
类名,
onClick,
…道具
}) {
let[active,setActive]=useState(false);
让timeoutIds=useRef([]);
let registerTimeout=(f,ms)=>{
let timeoutId=setTimeout(f,ms);
timeoutId.current.push(timeoutId);
};
让handleClick=e=>{
e、 坚持();
setActive(真);
if(typeof onClick==“function”)onClick(e);
};
让清理=()=>{
timeoutIds.current.forEach(clearTimeout);
};
useffect(()=>{
如果(活动===真){
registerTimeout(()=>setActive(false),250);
}
回流清理;
},[活跃];
返回(
);
}

stackblitz.com/fork/react你能在这里添加你的代码吗,这样我就可以有一个清晰的视图这里是一个警告的演示;嘿,丹!谢谢你的回答:)上面添加到代码演示中的代码片段确实清除了内存泄漏警告(哇!)但它似乎没有按照预期在超时后清除活动状态/类。我在此处更新:*我已使活动类在按钮上设置了红色背景色,以明确在单击按钮->查看状态更改的整个过程中发生了什么。如果我遗漏了一些明显的内容,请道歉!!这是版本n没有使用useffect+useRef进行比较:哦,糟糕,我犯了一个错误。修复了它!每次重新呈现组件时,超时都会被删除,而不仅仅是在卸载组件时。感谢您回复我/修改!:D!我还尝试添加更改,并且获得了超过最大调用堆栈大小的值:`var cancel=setTimeout.apply(未定义,[fn,ms].concat(args));`。这是通过实现
useSafetTimeout
函数,并通过要在handleClick中调用的函数设置此
const setTimeout
。有什么想法吗?我会继续探索,但这是当前的状态,这里有修订