![延迟在 延迟在](https://c1.lmlphp.com/image/static/default_avatar.gif)
本文介绍了使用 setTimeout 延迟在 useEffect 中反应 setInterval的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!
问题描述
我想在第一次触发时运行一个有延迟的间隔.我怎样才能用 useEffect 做到这一点?由于语法的原因,我发现很难实现我想要做的
区间函数
useEffect(()=>{const timer = setInterval(() => {//在这里做点什么返回 ()=>清除间隔(定时器)}, 1000);},[/*依赖*/])
延迟函数
useEffect(() => {setTimeout(() => {//我想在这里运行间隔,但它只会运行一次//因为没有依赖.如果我填充依赖项,//setTimeout 将运行多次.}, Math.random() * 1000);}, []);
当然可以以某种方式实现...
解决方案
入门
考虑解决组件的问题并编写小块.这里我们有一个 useInterval
自定义钩子,它严格定义了程序的 setInterval
部分.我添加了一些 console.log
行,以便我们可以观察效果 -
//草稿//继续阅读以确保所有部分都正确函数 useInterval (f, 延迟){ const [定时器,setTimer] =使用状态(空)const start = () =>{ 如果(定时器)返回console.log(开始")setTimer(setInterval(f, delay))}const stop = () =>{ if (!timer) 返回console.log(停止",计时器)setTimer(clearInterval(timer))}useEffect(() => stop, [])返回 [开始,停止,计时器!= null]}
现在当我们编写 MyComp
时,我们可以处理程序的 setTimeout
部分 -
function MyComp (props){ const [计数器,设置计数器] =使用状态(0)const [开始,停止,运行] =useInterval(_ => setCounter(x => x + 1), 1000)//第一次尝试返回 {柜台}<按钮onClick={开始}禁用={运行}儿童=开始"/><按钮onClick={停止}禁用={!running}儿童=停止"/>
}
现在我们可以在程序的各个部分useInterval
,并且每个部分都可以不同地使用.启动、停止和清理的所有逻辑都很好地封装在钩子中.
这是一个演示,您可以运行它来查看它的工作情况 -
const { useState, useEffect } = Reactconst useInterval = (f, delay) =>{ const [定时器,setTimer] =使用状态(未定义)const start = () =>{ 如果(定时器)返回console.log("开始")setTimer(setInterval(f, delay))}const stop = () =>{ if (!timer) 返回控制台日志(停止",计时器)setTimer(clearInterval(timer))}useEffect(() => stop, [])返回 [开始,停止,计时器!= null]}const MyComp = 道具 =>{ const [计数器,设置计数器] =使用状态(0)const [开始,停止,运行] =useInterval(_ => setCounter(x => x + 1), 1000)返回 {柜台}<按钮onClick={开始}禁用={运行}孩子=开始"/><按钮onClick={停止}禁用={!running}儿童=停止"/>
};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></脚本>
正确处理
我们希望确保我们的 useInterval
钩子不会在我们的计时器停止或我们的组件被移除后让任何定时函数运行.让我们在一个更严格的例子中测试它们,我们可以添加/删除许多计时器并随时启动/停止它们 -
需要对 useInterval
进行一些根本性的更改 -
函数 useInterval (f, delay = 1000){ const [busy, setBusy] = useState(0)useEffect(() => {//开始if (!busy) 返回设置忙(真)const t = setInterval(f, 延迟)//停止返回 () =>{设置忙(假)清除间隔(t)}}, [忙,延迟])返回 [_ =>setBusy(true),//开始_ =>setBusy(false),//停止忙碌//isBusy]}
在 MyTimer
组件中使用 useInterval
很直观.MyTimer
不需要对间隔进行任何类型的清理.清理由 useInterval
-
自动处理function MyTimer ({ delay = 1000, auto = true, ... props }){ const [计数器,设置计数器] =使用状态(0)const [开始,停止,忙碌] =useInterval(_ => {console.log("tick", Date.now())//<-- 用于演示设置计数器(x => x + 1)}, 延迟)useEffect(() => {console.log("delaying...")//<-- 用于演示setTimeout(() => {console.log("starting...")//<-- 用于演示汽车&&开始()}, 2000)}, [])返回 <span>{柜台}<button onClick={start} disabled={busy} children=Start"/><button onClick={stop} disabled={!busy} children=停止"/></span>}
Main
组件没有做任何特别的事情.它只是管理 MyTimer
组件的数组状态.不需要特定于计时器的代码或清理 -
const append = (a = [], x = null) =>[ ...a, x ]const remove = (a = [], x = null) =>{ const pos = a.findIndex(q => q === x)如果 (pos setTimers(r => append(r, ))const destroyTimer = c =>() =>setTimers(r => remove(r, c))返回 <main><按钮onClick={addTimer}儿童=添加计时器"/>{ timers.map((c, key) =><div key={key}>{C}<按钮onClick={destroyTimer(c)}儿童=破坏"/>
)}</main>}
展开下面的代码段以查看 useInterval
在您自己的浏览器中的运行情况.本演示建议使用全屏模式 -
const { useState, useEffect } = Reactconst append = (a = [], x = null) =>[ ...a, x ]const remove = (a = [], x = null) =>{ const pos = a.findIndex(q => q === x)如果 (pos {//开始if (!busy) 返回设置忙(真)const t = setInterval(f, 延迟)//停止返回 () =>{设置忙(假)清除间隔(t)}}, [忙,延迟])返回 [_ =>setBusy(true),//开始_ =>setBusy(false),//停止忙碌//isBusy]}函数 MyTimer ({ delay = 1000, auto = true, ... props }){ const [计数器,设置计数器] =使用状态(0)const [开始,停止,忙碌] =useInterval(_ => {console.log("tick", Date.now())设置计数器(x => x + 1)}, 延迟)useEffect(() => {console.log("延迟...")setTimeout(() => {console.log("开始...")汽车&&开始()}, 2000)}, [])返回 <span>{柜台}<按钮onClick={开始}禁用={忙}孩子=开始"/><按钮onClick={停止}禁用={!busy}儿童=停止"/></span>}函数主(){ const [timers, setTimers] = useState([])const addTimer = () =>setTimers(r => append(r, ))const destroyTimer = c =>() =>setTimers(r => remove(r, c))返回 <main><p>以扩展模式运行.打开您的开发者控制台</p><按钮onClick={addTimer}孩子=添加计时器"/>{ timers.map((c, key) =><div key={key}>{C}<按钮onClick={destroyTimer(c)}孩子=摧毁"/>
)}</main>}ReactDOM.render( <主/>, 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></脚本>
进阶
让我们想象一个更复杂的 useInterval
场景,其中定时函数 f
和 delay
可以改变 -
函数 useInterval (f, delay = 1000){ const [busy, setBusy] =//...const 间隔 = useRef(f)useEffect(() => {间隔.电流 = f}, [F])useEffect(() => {//开始//...常数 t =setInterval(_ => interval.current(), delay)//停止//...}, [忙,延迟])返回//...}
现在我们可以编辑MyTimer
来添加doubler
和turbo
状态-
function MyTimer ({ delay = 1000, auto = true, ... props }){ const [计数器,设置计数器] = useState(0)const [doubler, setDoubler] = useState(false)//<--const [turbo, setTurbo] = useState(false)//<--const [开始,停止,忙碌] =使用间隔( doubler//<-- doubler 改变运行的 f?_ =>设置计数器(x => x * 2): _ =>设置计数器(x => x + 1), turbo//<-- turbo 改变延迟?Math.floor(延迟/2): 延迟)//...
然后我们添加一个 和 按钮 -
//...const toggleTurbo = () =>setTurbo(t => !t)const toggleDouble = () =>setDoubler(t => !t)返回 <span>{柜台}{/* 开始按钮 ... */}<按钮onClick={toggleDoubler}//<--禁用={!busy}儿童={`倍频器:${倍频器?开":关闭"}`}/><按钮onClick={toggleTurbo}//<--禁用={!busy}孩子们={`涡轮增压:${涡轮增压?开":关闭"}`}/>{/* 停止按钮 ... */}</span>}
展开下面的代码片段以在您自己的浏览器中运行高级计时器演示 -
const { useState, useEffect, useRef, useCallback } = Reactconst append = (a = [], x = null) =>[ ...a, x ]const remove = (a = [], x = null) =>{ const pos = a.findIndex(q => q === x)如果 (pos {间隔.电流 = f}, [F])useEffect(() => {//开始if (!busy) 返回设置忙(真)常数 t =setInterval(_ => interval.current(), delay)//停止返回 () =>{设置忙(假)清除间隔(t)}}, [忙,延迟])返回 [_ =>setBusy(true),//开始_ =>setBusy(false),//停止忙碌//isBusy]}函数 MyTimer ({ delay = 1000, ... props }){ const [计数器,设置计数器] =使用状态(0)const [doubler, setDoubler] = useState(false)const [turbo, setTurbo] = useState(false)const [开始,停止,忙碌] =使用间隔( 倍增器?_ =>设置计数器(x => x * 2): _ =>设置计数器(x => x + 1), 涡轮增压?Math.floor(延迟/2): 延迟)const toggleTurbo = () =>setTurbo(t => !t)const toggleDouble = () =>setDoubler(t => !t)返回 <span>{柜台}<按钮onClick={开始}禁用={忙}孩子=开始"/><按钮onClick={toggleDoubler}禁用={!busy}儿童={`倍频器:${倍频器?开":关"}`}/><按钮onClick={toggleTurbo}禁用={!busy}孩子们={`涡轮增压:${涡轮增压?开":关"}`}/><按钮onClick={停止}禁用={!busy}儿童=停止"/></span>}函数主(){ const [timers, setTimers] = useState([])const addTimer = () =>setTimers(r => append(r, ))const destroyTimer = c =>() =>setTimers(r => remove(r, c))返回 <main><p>以扩展模式运行.打开您的开发者控制台</p><按钮onClick={addTimer}孩子=添加计时器"/>{ timers.map((c, key) =><div key={key}>{C}<按钮onClick={destroyTimer(c)}孩子=摧毁"/>
)}</main>}ReactDOM.render( <主/>, 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></脚本>
I want to run an interval with a delay for the first time it fires. How can I do this with useEffect? Because of the syntax I've found it difficult to achieve what I want to do
The interval function
useEffect(()=>{
const timer = setInterval(() => {
//do something here
return ()=> clearInterval(timer)
}, 1000);
},[/*dependency*/])
The delay function
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);
}, []);
Sure it is achievable somehow...
解决方案
getting started
Consider detangling the concerns of your component and writing small pieces. Here we have a useInterval
custom hook which strictly defines the setInterval
portion of the program. I added some console.log
lines so we can observe the effects -
// 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]
}
Now when we write MyComp
we can handle the setTimeout
portion of the program -
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>
}
Now we can useInterval
in various parts of our program, and each one can be used differently. All the logic for the start, stop and cleanup is nicely encapsulated in the hook.
Here's a demo you can run to see it working -
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")
)
<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>
getting it right
We want to make sure our useInterval
hook doesn't leave any timed functions running if our timer are stopped or after our components are removed. Let's test them out in a more rigorous example where we can add/remove many timers and start/stop them at any time -
A few fundamental changes were necessary to make to 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
]
}
Using useInterval
in MyTimer
component is intuitive. MyTimer
is not required to do any sort of cleanup of the interval. Cleanup is automatically handled by 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>
}
The Main
component doesn't do anything special. It just manages an array state of MyTimer
components. No timer-specific code or clean up is required -
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>
}
Expand the snippet below to see useInterval
working in your own browser. Fullscreen mode is recommended for this demo -
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>
getting advanced
Let's imagine an even more complex useInterval
scenario where the timed function, f
, and the delay
can change -
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 // ...
}
Now we can edit MyTimer
to add the doubler
and turbo
state -
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
)
// ...
Then we add a and button -
// ...
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>
}
Expand the snippet below to run the advanced timer demo in your own browser -
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>
这篇关于使用 setTimeout 延迟在 useEffect 中反应 setInterval的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!