在 useEffect 中使用 setTimeout 延迟响应 setInterval

ion*_*ush 8 settimeout setinterval reactjs react-hooks use-effect

我想在第一次触发时运行一个延迟的间隔。我该如何使用 useEffect 来做到这一点?由于语法,我发现很难实现我想做的事情

区间函数

  useEffect(()=>{
    const timer = setInterval(() => {
      //do something here
      return ()=> clearInterval(timer)
    }, 1000);
  },[/*dependency*/])
Run Code Online (Sandbox Code Playgroud)

延迟功能

useEffect(() => {
    setTimeout(() => {
//I want to run the interval here, but it will only run once 
//because of no dependencies. If i populate the dependencies, 
//setTimeout will run more than once.
}, Math.random() * 1000);
  }, []);
Run Code Online (Sandbox Code Playgroud)

当然,它是可以以某种方式实现的......

Tha*_*you 7

入门

考虑理清组件的关注点并编写小片段。这里我们有一个useInterval自定义钩子,它严格定义了setInterval程序的部分。我添加了一些console.log行,以便我们可以观察效果 -

// rough draft
// read on to make sure we get all the parts right
function useInterval (f, delay)
{ const [timer, setTimer] =
    useState(null)
  
  const start = () =>
  { if (timer) return
    console.log("started")
    setTimer(setInterval(f, delay))
  }
  
  const stop = () =>
  { if (!timer) return
    console.log("stopped", timer)
    setTimer(clearInterval(timer))
  }
    
  useEffect(() => stop, [])
  
  return [start, stop, timer != null]
}
Run Code Online (Sandbox Code Playgroud)

现在,当我们编写时MyComp,我们可以处理setTimeout程序的一部分 -

function MyComp (props)
{ const [counter, setCounter] =
    useState(0)
    
  const [start, stop, running] =
    useInterval(_ => setCounter(x => x + 1), 1000) // first try
  
  return <div>
    {counter}
    <button
      onClick={start}
      disabled={running}
      children="Start"
    />
    <button
      onClick={stop}
      disabled={!running}
      children="Stop"
    />
  </div>
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以useInterval在程序的各个部分中使用,并且每个部分都可以以不同的方式使用。所有启动、停止和清理的逻辑都很好地封装在钩子中。

这是一个演示,您可以运行来查看它的工作情况 -

const { useState, useEffect } = React

const useInterval = (f, delay) =>
{ const [timer, setTimer] =
    useState(undefined)
  
  const start = () =>
  { if (timer) return
    console.log("started")
    setTimer(setInterval(f, delay))
  }
  
  const stop = () =>
  { if (!timer) return
    console.log("stopped", timer)
    setTimer(clearInterval(timer))
  }
    
  useEffect(() => stop, [])
  
  return [start, stop, timer != null]
}
  
const MyComp = props =>
{ const [counter, setCounter] =
    useState(0)
    
  const [start, stop, running] =
    useInterval(_ => setCounter(x => x + 1), 1000)
  
  return <div>
    {counter}
    <button
      onClick={start}
      disabled={running}
      children="Start"
    />
    <button
      onClick={stop}
      disabled={!running}
      children="Stop"
    />
  </div>
};


ReactDOM.render
  ( <MyComp/>
  , document.getElementById("react")
  )
Run Code Online (Sandbox Code Playgroud)
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script></script>
Run Code Online (Sandbox Code Playgroud)


做对

useInterval如果我们的计时器停止或在我们的组件被删除后,我们希望确保我们的钩子不会让任何定时函数运行。让我们在一个更严格的示例中测试它们,我们可以添加/删除许多计时器并随时启动/停止它们 -

添加/删除计时器

有必要进行一些根本性的改变useInterval——

function useInterval (f, delay = 1000)
{ const [busy, setBusy] = useState(0)
  
  useEffect(() => {
    // start
    if (!busy) return
    setBusy(true)
    const t = setInterval(f, delay)
    // stop
    return () => {
      setBusy(false)
      clearInterval(t)
    }
  }, [busy, delay])
  
  return [
    _ => setBusy(true),  // start
    _ => setBusy(false), // stop
    busy                 // isBusy
  ]
}
Run Code Online (Sandbox Code Playgroud)

useInterval在组件中使用MyTimer很直观。MyTimer不需要对间隔进行任何类型的清理。useInterval清理工作由-自动处理

function MyTimer ({ delay = 1000, auto = true, ... props })
{ const [counter, setCounter] =
    useState(0)
    
  const [start, stop, busy] =
    useInterval(_ => {
      console.log("tick", Date.now()) // <-- for demo
      setCounter(x => x + 1)
    }, delay)

  useEffect(() => {
    console.log("delaying...") // <-- for demo
    setTimeout(() => {
      console.log("starting...") // <-- for demo
      auto && start()
    }, 2000)
  }, [])
  
  return <span>
    {counter}
    <button onClick={start} disabled={busy} children="Start" />
    <button onClick={stop} disabled={!busy} children="Stop" />
  </span>
}
Run Code Online (Sandbox Code Playgroud)

Main组件没有做任何特殊的事情。它只是管理组件的数组状态MyTimer。不需要特定于计时器的代码或清理 -

const append = (a = [], x = null) =>
  [ ...a, x ]
  
const remove = (a = [], x = null) =>
{ const pos = a.findIndex(q => q === x)
  if (pos < 0) return a
  return [ ...a.slice(0, pos), ...a.slice(pos + 1) ]
}

function Main ()
{ const [timers, setTimers] = useState([])
  
  const addTimer = () =>
    setTimers(r => append(r, <MyTimer />))
    
  const destroyTimer = c => () =>
    setTimers(r => remove(r, c))
  
  return <main>
    <button
      onClick={addTimer}
      children="Add Timer"
    />
    { timers.map((c, key) =>
      <div key={key}>
        {c}
        <button
          onClick={destroyTimer(c)} 
          children="Destroy"
        />
      </div>
    )}
  </main>
}
Run Code Online (Sandbox Code Playgroud)

展开下面的代码片段以查看useInterval在您自己的浏览器中的工作情况。建议本演示使用全屏模式 -

const { useState, useEffect } = React

const append = (a = [], x = null) =>
  [ ...a, x ]
  
const remove = (a = [], x = null) =>
{ const pos = a.findIndex(q => q === x)
  if (pos < 0) return a
  return [ ...a.slice(0, pos), ...a.slice(pos + 1) ]
}

function useInterval (f, delay = 1000)
{ const [busy, setBusy] = useState(0)
  
  useEffect(() => {
    // start
    if (!busy) return
    setBusy(true)
    const t = setInterval(f, delay)
    // stop
    return () => {
      setBusy(false)
      clearInterval(t)
    }
  }, [busy, delay])
  
  return [
    _ => setBusy(true),  // start
    _ => setBusy(false), // stop
    busy                 // isBusy
  ]
}

function MyTimer ({ delay = 1000, auto = true, ... props })
{ const [counter, setCounter] =
    useState(0)
    
  const [start, stop, busy] =
    useInterval(_ => {
      console.log("tick", Date.now())
      setCounter(x => x + 1)
    }, delay)

  useEffect(() => {
    console.log("delaying...")
    setTimeout(() => {
      console.log("starting...")
      auto && start()
    }, 2000)
  }, [])
  
  return <span>
    {counter}
    <button
      onClick={start}
      disabled={busy}
      children="Start"
    />
    <button
      onClick={stop}
      disabled={!busy}
      children="Stop"
    />
  </span>
}

function Main ()
{ const [timers, setTimers] = useState([])
  
  const addTimer = () =>
    setTimers(r => append(r, <MyTimer />))
    
  const destroyTimer = c => () =>
    setTimers(r => remove(r, c))
  
  return <main>
    <p>Run in expanded mode. Open your developer console</p>
    <button
      onClick={addTimer}
      children="Add Timer"
    />
    { timers.map((c, key) =>
      <div key={key}>
        {c}
        <button
          onClick={destroyTimer(c)} 
          children="Destroy"
        />
      </div>
    )}
  </main>
}

ReactDOM.render
  ( <Main/>
  , document.getElementById("react")
  )
Run Code Online (Sandbox Code Playgroud)
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script></script>
Run Code Online (Sandbox Code Playgroud)


取得进步

让我们想象一个更复杂的useInterval场景,其中定时函数fdelay可以改变 -

高级定时器

function useInterval (f, delay = 1000)
{ const [busy, setBusy] = // ...
  const interval = useRef(f)

  useEffect(() => {
    interval.current = f
  }, [f])
  
  useEffect(() => {
    // start
    // ...
    const t =
      setInterval(_ => interval.current(), delay)
      
    // stop
    // ...
  }, [busy, delay])
  
  return // ...
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以编辑MyTimer以添加doublerturbo状态 -

function MyTimer ({ delay = 1000, auto = true, ... props })
{ const [counter, setCounter] = useState(0)
  
  const [doubler, setDoubler] = useState(false) // <--
  const [turbo, setTurbo] = useState(false)     // <--
  
  const [start, stop, busy] =
    useInterval
      ( doubler   // <-- doubler changes which f is run
          ? _ => setCounter(x => x * 2)
          : _ => setCounter(x => x + 1)
      , turbo     // <-- turbo changes delay
          ? Math.floor(delay / 2)
          : delay
      )

  // ...

Run Code Online (Sandbox Code Playgroud)

然后我们添加一个doubleturbo按钮 -

  // ...
  const toggleTurbo = () =>
    setTurbo(t => !t)
    
  const toggleDoubler = () =>
    setDoubler(t => !t)
  
  return <span>
    {counter}
    {/* start button ... */}
    <button
      onClick={toggleDoubler}  // <--
      disabled={!busy}
      children={`Doubler: ${doubler ? "ON" : "OFF"}`}
    />
    <button
      onClick={toggleTurbo}    // <--
      disabled={!busy}
      children={`Turbo: ${turbo ? "ON" : "OFF"}`}
    />
    {/* stop button ... */}
  </span>
}
Run Code Online (Sandbox Code Playgroud)

展开下面的代码片段以在您自己的浏览器中运行高级计时器演示 -

const { useState, useEffect, useRef, useCallback } = React

const append = (a = [], x = null) =>
  [ ...a, x ]
  
const remove = (a = [], x = null) =>
{ const pos = a.findIndex(q => q === x)
  if (pos < 0) return a
  return [ ...a.slice(0, pos), ...a.slice(pos + 1) ]
}

function useInterval (f, delay = 1000)
{ const interval = useRef(f)
  const [busy, setBusy] = useState(0)
  
  useEffect(() => {
    interval.current = f
  }, [f])
  
  useEffect(() => {
    // start
    if (!busy) return
    setBusy(true)
    const t =
      setInterval(_ => interval.current(), delay)
      
    // stop
    return () => {
      setBusy(false)
      clearInterval(t)
    }
  }, [busy, delay])
  
  return [
    _ => setBusy(true),  // start
    _ => setBusy(false), // stop
    busy                 // isBusy
  ]
}

function MyTimer ({ delay = 1000, ... props })
{ const [counter, setCounter] =
    useState(0)
  
  const [doubler, setDoubler] = useState(false)
  const [turbo, setTurbo] = useState(false)
  
  const [start, stop, busy] =
    useInterval
      ( doubler
          ? _ => setCounter(x => x * 2)
          : _ => setCounter(x => x + 1)
      , turbo
          ? Math.floor(delay / 2)
          : delay
      )
      
  const toggleTurbo = () =>
    setTurbo(t => !t)
    
  const toggleDoubler = () =>
    setDoubler(t => !t)
  
  return <span>
    {counter}
    <button
      onClick={start}
      disabled={busy}
      children="Start"
    />
    <button
      onClick={toggleDoubler}
      disabled={!busy}
      children={`Doubler: ${doubler ? "ON" : "OFF"}`}
    />
    <button
      onClick={toggleTurbo}
      disabled={!busy}
      children={`Turbo: ${turbo ? "ON" : "OFF"}`}
    />
    <button
      onClick={stop}
      disabled={!busy}
      children="Stop"
    />
  </span>
}

function Main ()
{ const [timers, setTimers] = useState([])
  
  const addTimer = () =>
    setTimers(r => append(r, <MyTimer />))
    
  const destroyTimer = c => () =>
    setTimers(r => remove(r, c))
  
  return <main>
    <p>Run in expanded mode. Open your developer console</p>
    <button
      onClick={addTimer}
      children="Add Timer"
    />
    { timers.map((c, key) =>
      <div key={key}>
        {c}
        <button
          onClick={destroyTimer(c)} 
          children="Destroy"
        />
      </div>
    )}
  </main>
}

ReactDOM.render
  ( <Main/>
  , document.getElementById("react")
  )
Run Code Online (Sandbox Code Playgroud)
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script></script>
Run Code Online (Sandbox Code Playgroud)


Jai*_*ipi 0

这是您想要实现的目标吗?useeffect 上的空数组告诉它将在渲染元素后运行此代码

const {useState, useEffect} = React;
// Example stateless functional component
const SFC = props => {
  
  const [value,setvalue] = useState('initial')
  const [counter,setcounter] = useState(0)
  

  useEffect(() => {
    const timer = setInterval(() => {
    setvalue('delayed value')
    setcounter(counter+1)
    clearInterval(timer)
    }, 2000);
  }, []);
  
  return(<div>
          Value:{value} | counter:{counter}
         </div>)
};

// Render it
ReactDOM.render(
  <SFC/>,
  document.getElementById("react")
);
Run Code Online (Sandbox Code Playgroud)
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script></script>
Run Code Online (Sandbox Code Playgroud)