首页 > 解决方案 > 自定义钩子:不可变的 useState 来存储函数并避免无限循环

问题描述

是否可以使用 useState 挂钩来存储包含函数的不可变对象,以在自定义挂钩中更新另一个状态?

我有一个自定义 Hook,用于简化应用程序中 Timeouts 的使用:

export const useTimer = () => {
  const [timer, setTimerObject] = useState<NodeJS.Timeout | null>(null)
  
  //Function to set a new timer. If a timer is already present it is
  //canceled and substituted (useEffect)
  function setTimer(callback:(...args:any[])=>void,ms:number){
    setTimerObject(setTimeout(callback,ms))
  }

  //Function to clear the timer (if there's one)
  function clearTimer(){
    setTimerObject(null);
  }

  //shouldComponentUpdate: delete previous timer if a new timer is issued
  //work also as componentWillUnmount
  useEffect(() => {
    return () => {
      if (timer) clearTimeout(timer)
    }
  }, [timer])

  return ({
    setTimer,
    clearTimer
  })
}

因为我在钩子结束时返回一个对象,所以每次我在 useEffect 钩子中设置一个新计时器时,它都会启动一个无限循环。

const timer = useTimer();

 useEffect(() => {
    if (showError===null) { //Just a random condition to
      setShowError("My error");
      timer.setTimer(() => setShowError(null), 2000) //INFINITE LOOP
    }
  }, [timer, showError])

为了避免在每个设置状态下重新渲染,我修改了我的钩子以使用包含更新计时器的函数的固定状态。我没有使用两个 useCallback() 钩子,因为如果我的自定义钩子返回一个包含两个回调的对象,则回调地址保持不变,但指向回调的对象将在每次 setState 调用时发生变化,从而产生无限循环。但是,通过使用不可变状态 Hook,对象引用不会改变,从而允许我的自定义钩子避免无限循环。这种方法是否会导致可能导致我的代码出现意外行为的问题,或者是否存在满足此特定用例的反应挂钩?

export const useTimer = () => {
  const [timer, setTimerObject] = useState<NodeJS.Timeout | null>(null);
  //HERE THE IMMUTABLE STATE OBJECT
  const [functions] = useState({
    setTimer : (callback:(...args:any[])=>void,ms:number)=>{
      setTimerObject(setTimeout(callback,ms))
    },
    clearTimer : ()=>{
      setTimerObject(null)
    }
  });
  
  //shouldComponentUpdate: delete previous timer if a new timer is issued
  //work also as componentWillUnmount
  useEffect(() => {
    return () => {
      if (timer) clearTimeout(timer)
    }
  }, [timer])


  return functions;
}

标签: reactjsreact-hooks

解决方案


尝试这样的事情

const useTimer = () => { 
    const ref = useRef({ 
      setTimer: function setTimer(callback:(...args:any[])=>void,ms:number){ 
        setTimerObject(setTimeout(callback,ms)) 
      },
      clearTimer: function clearTimer(){ 
        setTimerObject(null);
      } 
    }); 
    return ref.current; 
}

推荐阅读