First of all, setInterval is Hook encapsulated 👇

import { useEffect, useRef } from 'react'

/** * interTerval hooks component *@param Fn executes the function *@param Delay time *@param Options immediate If the value is true, the fn function is executed immediately and then the timer */ is executed
function useInterval(
  fn: () => void,
  delay: number | null | undefined, options? : { immediate? :boolean
  }
) :void {
  constimmediate = options? .immediateconst timerRef = useRef<() = > void>()

  timerRef.current = fn

  useEffect(() = > {
    if (delay === undefined || delay === null) {
      return
    }
    if(immediate) { timerRef.current? (1)}.const timer = setInterval(() = >{ timerRef.current? .() }, delay)return () = > {
      clearInterval(timer)
    }
  }, [delay])
}

export default useInterval
Copy the code

Implement countdown Hook

import { useState, useEffect, useRef, useMemo } from 'react'
import { useInterval } from '/'

interface ITime {
  /** The current time */currentTime? :number
  /** End time */endTime? :number
  /** Another way to pass the current time and end time, directly pass the time difference */differTime? :number
}

interface ICbTime {
  d: number
  h: number
  m: number
  s: number
}

/** ** **@param Options Time object *@param Cb the callback function executed when the countdown is complete@param NoImmediate Specifies whether to execute the callback immediately when the callback conditions are met. The default value is false
function useCountDown(options: ITime, cb? : () = >void, noImmediate? :boolean
) :ICbTime {
  const { currentTime = 0, endTime = 0, differTime = 0 } = options
  const [diffTime, setDiffTime] = useState(0)
  /** The time when the component receives the parameter */
  const entryTime = useRef<number> (0)
  /** The time difference required for the current countdown */
  const maxTime = useRef<number> (0)
  /** Whether a callback can be performed */
  const isImplementCb = useRef(false)

  useEffect(() = > {
    if(! isImplementCb.current) { isImplementCb.current =true
    }
    if ((currentTime > 0 && endTime > 0) || differTime > 0) {
      entryTime.current = new Date().getTime()
      maxTime.current = differTime > 0 ? differTime : endTime - currentTime
      if (maxTime.current <= 0 && noImmediate) {
        isImplementCb.current = false
      }
      setDiffTime(maxTime.current)
    }
  }, [currentTime, endTime, differTime])

  useInterval(
    () = > {
      const curtTimes = new Date().getTime()
      const TimeDifference = curtTimes - entryTime.current
      setDiffTime(maxTime.current - TimeDifference)
    },
    diffTime <= 0 ? null : 1000
  )

  const timeObj = useMemo(() = > {
    const time = diffTime > 0 ? diffTime / 1000 : 0
    const d = Math.floor(time / (24 * 60 * 60))
    const h = Math.floor((time / (60 * 60)) % 24)
    const m = Math.floor((time / 60) % 60)
    const s = Math.ceil(time % 60)

    if (diffTime <= 0 && isImplementCb.current) {
      /** * setTimeout:  * annot update during an existing state transition (such as within `render`). * Render methods should be a pure function of props and state. */
      setTimeout(() = >{ cb? . ()},0)}return { d, h, m, s }
  }, [diffTime])

  return timeObj || ({} as ICbTime)
}

export default useCountDown
Copy the code

Write a demo to see the effect 👇

  const TimeArea = () = > {
    const { d, h, m, s } = useCountDown(
      {
        currentTime: 1631262176333.endTime: 1831062176333
      },
      () = > {
        alert('Countdown is over')})return (
      <div style={{ width: '200px', height: '200px' }}>{d} days to the end of the mission<i>{h < 10 ? '0' + h : h}</i>:
        <i>{m < 10 ? '0' + m : m}</i>:<i>{s < 10 ? '0' + s : s}</i>
      </div>)}Copy the code