Je souhaite exécuter un intervalle avec un délai pour la première fois qu'il se déclenche. Comment puis-je faire cela avec useEffect? En raison de la syntaxe, j'ai eu du mal à réaliser ce que je voulais faire
La fonction d'intervalle
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);
}, []);
La fonction de retard
useEffect(()=>{
const timer = setInterval(() => {
//do something here
return ()=> clearInterval(timer)
}, 1000);
},[/*dependency*/])
4 Réponses :
Est-ce ce que vous voulez réaliser? le tableau vide sur useeffect indique qu'il exécutera ce code une fois l'élément rendu
<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>
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")
);
Non, je veux exécuter un setInterval
J'ai édité l'extrait, vérifiez si c'est ce que vous voulez, j'ai ajouté un compteur pour vérifier si l'intervalle est arrêté après l'utilisation de clearInterval
Je pense que ce que vous essayez de faire est le suivant:
<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>
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"));
Merci. Un des problèmes que j'utilisais avec les valeurs useState dans la fermeture setInterval et les valeurs étaient périmées, j'ai donc dû utiliser la valeur setState (latestState => {}) latestState // par exemple. [state, setState] = useState ("") pour récupérer le dernier état
pour commencer
Pensez à démêler les préoccupations de votre composant et à écrire de petits morceaux. Ici, nous avons un hook personnalisé useInterval qui définit strictement la partie setInterval du programme. J'ai ajouté quelques lignes console.log afin que nous puissions observer les effets -
<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>
Maintenant, quand nous écrivons MyComp , nous pouvons gérer la partie setTimeout du programme -
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")
) Nous pouvons maintenant useInterval dans diverses parties de notre programme, et chacune on peut être utilisé différemment. Toute la logique du démarrage, de l'arrêt et du nettoyage est bien encapsulée dans le hook.
Voici une démo que vous pouvez exécuter pour la voir fonctionner -
// ...
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>
}
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
)
// ...
faire les choses correctement
Nous voulons nous assurer que notre hook useInterval ne laisse aucune fonction chronométrée en cours d'exécution si notre minuterie est arrêtée ou après le retrait de nos composants. Testons-les dans un exemple plus rigoureux où nous pouvons ajouter / supprimer de nombreux minuteries et les démarrer / les arrêter à tout moment -
Quelques modifications fondamentales ont été nécessaires à apporter à useInterval -
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 // ...
}
Utilisation de useInterval dans MyTimer est intuitif. MyTimer n'est pas nécessaire pour effectuer un quelconque nettoyage de l'intervalle. Le nettoyage est automatiquement géré par useInterval -
<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>
Le composant Main ne fait rien de spécial. Il gère simplement un état de tableau de composants MyTimer . Aucun code ou nettoyage spécifique à la minuterie n'est requis -
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")
) Développez l'extrait ci-dessous pour voir useInterval fonctionner dans votre propre navigateur. Le mode plein écran est recommandé pour cette démo -
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>
}
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>
}
avancer
Imaginons un scénario useInterval encore plus complexe où la fonction chronométrée, f , et le délai peut changer -
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
]
}
Maintenant, nous pouvons modifier MyTimer pour ajouter l'état doubler et turbo -
<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>
Ensuite nous ajoutons un bouton double et turbo -
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")
)Développez l'extrait ci-dessous pour exécuter la démo avancée de la minuterie navigateur -
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>
}
// 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]
}
Si vous essayez d'utiliser un setInterval à l'intérieur de useEffect , je pense que vous avez un peu changé l'ordre, ça devrait être comme ça
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)
})
setIntervala déjà un délai à la première fois, avez-vous besoin d'un délai d'expiration différent de la période d'intervalle?ionush, ce n'est pas un problème trivial. voir ma réponse pour une explication