Existe-t-il une alternative à garder une "horloge" en arrière-plan pour implémenter auto-next (après quelques secondes) dans le carrousel en utilisant des crochets de réaction?
Le crochet de réaction personnalisé ci-dessous implémente un état pour un carrousel qui prend en charge les méthodes manuelles (suivant, précédent, réinitialiser) et automatiques (démarrer, arrêter) pour changer l'index actuel (actif) du carrousel.
const useCarousel = (items = []) => {
const [current, setCurrent] = useState(
items && items.length > 0 ? 0 : undefined
);
const [auto, setAuto] = useState(false);
const next = () => setCurrent((current + 1) % items.length);
const prev = () => setCurrent(current ? current - 1 : items.length - 1);
const reset = () => setCurrent(0);
const start = _ => setAuto(true);
const stop = _ => setAuto(false);
useEffect(() => {
const interval = setInterval(_ => {
if (auto) {
next();
} else {
// do nothing
}
}, 3000);
return _ => clearInterval(interval);
});
return {
current,
next,
prev,
reset,
start,
stop
};
};
Parce que la valeur current
va changer à chaque "intervalle" tant que devrait être en cours d'exécution, votre code va alors démarrer et arrêter un nouveau minuteur à chaque rendu. Vous pouvez voir ceci en action ici:
https://codesandbox.io/s/03xkkyj19w
Vous pouvez changer setInterval
en setTimeout
et vous obtiendrez exactement le même comportement. setTimeout
n'est pas une horloge persistante, mais cela n'a pas d'importance, car ils sont tous deux nettoyés de toute façon.
Si vous ne voulez pas lancer de minuteur, mettez la condition avant setInterval
et non à l'intérieur.
useEffect(
() => {
let id;
if (run) {
id = setInterval(() => {
setValue(value + 1)
}, 1000);
}
return () => {
if (id) {
alert(id) // notice this runs on every render and is different every time
clearInterval(id);
}
};
}
);
Il existe des différences entre setInterval
et setTimeout
que vous ne souhaitez peut-être pas perdre en redémarrant toujours votre minuterie lorsque le composant effectue un nouveau rendu. Ce violon montre la différence de dérive entre les deux (et cela ne prend même pas en compte tous les calculs effectués par React, ce qui pourrait jeter un peu plus loin le tout).
En vous référant maintenant à votre réponse , Marco, l'utilisation de setInterval
est totalement perdue, car les effets sans conditions disposent et sont réexécutés chaque fois que le composant effectue un nouveau rendu. Ainsi, dans votre premier exemple, l'utilisation de la dépendance current
entraîne la suppression et la réexécution de cet effet chaque fois que la variable current
change (chaque fois que l'intervalle est exécuté). Le second fait la même chose, mais chaque fois qu’un état change (ce qui provoque un re-rendu), un comportement inattendu peut survenir. La seule raison pour laquelle on travaille est parce que next()
provoque un changement d'état.
Considérant le fait que vous n'êtes probablement pas concerné par le timing exact, il est préférable d'utiliser setTimeout
de manière simple, en utilisant les variables current
et auto
comme dépendances. Donc, pour reformuler une partie de votre réponse, procédez comme suit:
useEffect(
() => {
if (!auto) return;
const interval = setInterval(_ => {
next();
}, autoInterval);
return _ => clearInterval(interval);
},
[auto, current]
);
Notez que je pense que vous pourriez remplacer la dépendance de
current
parnext
puisque cela représente plus directement ce que vous faites dansuseEffect
. Mais je ne suis pas sûr à 100% de la façon dont React diffère ces dépendances, donc je le laisse tel quel.
Généralement, pour ceux qui lisent simplement cette réponse et veulent un moyen de créer un simple minuteur, voici une version qui ne prend pas en compte le code d'origine du PO, ni leur besoin de pouvoir démarrer et arrêter le minuteur indépendamment:
const [counter, setCounter] = useState(0);
useEffect(
() => {
const id= setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearTimer(id);
};
},
[counter],
);
Cependant, vous vous demandez peut-être comment utiliser un intervalle plus exact, étant donné que setTimeout
peut dériver plus que setInterval
. Voici une méthode, encore une fois, générique sans utiliser le code de l'OP:
const [counter, setCounter] = useState(30);
const r = useRef(null);
r.current = { counter, setCounter };
useEffect(
() => {
const id = setInterval(() => {
r.current.setCounter(r.current.counter + 1);
}, 1000);
return () => {
clearInterval(id);
};
},
['once'],
);
Que se passe-t-il ici? Eh bien, pour obtenir le rappel de setInterval
afin de toujours faire référence à la version actuellement acceptable de setCounter
, nous avons besoin d’un état mutable. Réagir nous donne ceci avec useRef
. La fonction useRef
renverra un objet ayant une propriété current
. Nous pouvons ensuite définir cette propriété (ce qui se produira chaque fois que le composant effectuera un nouveau rendu) sur les versions actuelles de counter
et setCounter
.
Ensuite, pour éviter que l'intervalle ne soit supprimé à chaque rendu, nous ajoutons à useEffect
une dépendance dont la garantie est de ne jamais changer. Dans mon cas, j'aime bien utiliser la chaîne "once"
pour indiquer que je force cet effet à être configuré une seule fois. L'intervalle sera toujours supprimé lorsque le composant sera démonté.
Donc, en appliquant ce que nous savons à la question initiale du PO, vous pouvez utiliser setInterval
pour un diaporama moins susceptible de dériver comme ceci:
// ... OP's implementation code including `autoInterval`,
// `auto`, and `next` goes above here ...
const r = useRef(null);
r.current = { next };
useEffect(
() => {
if (!auto) return;
const id = setInterval(() => {
r.current.next();
}, autoInterval);
return () => {
clearInterval(id);
};
},
[auto],
);
Jusqu'à présent, il semble que les deux solutions ci-dessous fonctionnent comme souhaité:
Création conditionnelle de la minuterie - cela nécessite que useEffect dépend à la fois de auto
et de current
pour fonctionner
useEffect(
() => {
if (!auto) return;
const interval = setInterval(_ => {
next();
}, autoInterval);
return _ => clearInterval(interval);
},
[auto, current]
);
Exécution conditionnelle de la mise à jour vers l'état - elle ne nécessite pas de dépendances useEffect
useEffect(() => {
const interval = setInterval(_ => {
if (auto) {
next();
} else {
// do nothing
}
}, autoInterval);
return _ => clearInterval(interval);
});
Les deux solutions fonctionnent si nous remplaçons setInterval
par setTimeout
Vous pouvez utiliser le crochet useTimeout
qui renvoie true
après le nombre spécifié de millisecondes.