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) })
setInterval
a 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