在useEffect中使用setTimeout延迟的React setInterval。

问题描述 投票:0回答:1

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

区间函数

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

延时功能

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);
  }, []);

当然是可以实现的... ...

reactjs react-hooks settimeout setinterval use-effect
1个回答
1
投票

我想你想做的是这样的。

const DelayTimer = props => {
  const [value, setvalue] = React.useState("initial");
  const [counter, setcounter] = React.useState(0);

  React.useEffect(() => {
    let timer;
    setTimeout(() => {
      setvalue("delayed value");
      timer = setInterval(() => {
        setcounter(c => c + 1);
      }, 1000);
    }, 2000);
    return () => clearInterval(timer);
  }, []);

  return (
    <div>
      Value:{value} | counter:{counter}
    </div>
  );
};

// Render it
ReactDOM.render(<DelayTimer />, document.getElementById("react"));
<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>

0
投票

如果你想使用 setInterval 里面 useEffect我觉得你把顺序调换了一下,应该是这样的。

const INTERVAL_DELAY = 1000
useEffect(() => {
  const interval = setInterval(() => {
  /* do repeated stuff */
  }, INTERVAL_DELAY)
  return () => clearInterval(interval)
})

间隔将在延时后开始,所以如果你想让间隔延时为X秒的间隔在Y秒后开始,你必须在setTimeout中实际使用延时为Y - X。

const INITIAL_DELAY = 10000
const INTERVAL_DELAY = 5000

useEffect(() => {
  let interval
  setTimeout(() => {
    const interval = setInterval(() => {
    /* do repeated stuff */
    }, INTERVAL_DELAY)
  }, INITIAL_DELAY - INTERVAL_DELAY)
  return () => clearInterval(interval)
})


0
投票

起步

可以考虑疏导你组件的关注点,写小件。这里我们有一个 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]
}

现在当我们写 MyComp 我们可以处理 setTimeout 该计划的一部分----

function MyComp (props)
{ const [counter, setCounter] =
    useState(0)

  const [start, stop, running] =
    useInterval(_ => setCounter(x => x + 1), 1000) // first try at useInterval

  useEffect(() => {
    console.log("delaying...")
    setTimeout(() => {
      console.log("starting...")
      !running && start()
    }, 2000)
  }, [])

  return <div>
    {counter}
    <button
      onClick={start}
      disabled={running}
      children="Start"
    />
    <button
      onClick={stop}
      disabled={!running}
      children="Stop"
    />
  </div>
}

现在我们可以 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]
}
  
const MyComp = props =>
{ const [counter, setCounter] =
    useState(0)
    
  const [start, stop, running] =
    useInterval(_ => setCounter(x => x + 1), 1000)

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


ReactDOM.render
  ( <MyComp/>
  , document.getElementById("react")
  )
<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>

得心应手

我们要确保我们的 useInterval 钩子不会在我们的定时器被停止或组件被移除后留下任何定时函数运行。让我们在一个更严格的例子中测试一下,我们可以添加remove许多定时器,并在任何时候开始停止它们--。

add/remove timers

有必要进行一些根本性的改变,以 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
  ]
}

使用 useIntervalMyTimer 组件是直观的。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>
}

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>
}

展开下面的片段查看 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")
  )
<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>

进阶

让我们想象一个更复杂的 useInterval 定时功能的情况下。fdelay 可以改变

advanced timers

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 // ...
}

现在我们可以编辑 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
      )

  // ...

然后我们添加一个 双重涡轮增压 纽扣

  // ...
  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>
}

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

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")
  )
<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>

-1
投票

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")
);
<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>
© www.soinside.com 2019 - 2024. All rights reserved.