Partie 1. Crochet personnalisé
À propos des générateurs
Les générateurs sont un nouveau type de fonction qui a été introduit dans ES6. De nombreux articles ont été écrits à leur sujet et de nombreux exemples théoriques sont donnés. Quant à moi, le livre You don't know JS , part of async & performance a aidé à clarifier l'essence des générateurs et comment les utiliser . De tous les livres JS que j'ai étudiés, celui-ci est le plus rempli d'informations utiles sans eau.
Imaginez que le générateur (la fonction dans la déclaration, qui est *) est une sorte d'appareil électrique avec un panneau de commande à distance. Après avoir créé et monté le générateur (déclaration de fonction), vous devez le "faire tourner" (exécuter cette fonction) pour qu'il tourne au ralenti et "alimente" le panneau de contrôle par lui-même (lorsque la fonction du générateur est exécutée, il renvoie un itérateur). Cette télécommande a deux boutons: Démarrer (appeler la méthode suivante de l'itérateur pour la première fois) et Suivant (appels suivants à la méthode suivante de l'itérateur). Ensuite, avec ce panneau de commande, vous pouvez vous précipiter dans toute la centrale (selon notre application) et lorsque vous avez besoin d'énergie électrique (certaines valeurs de la fonction générateur), appuyez sur le bouton suivant de la télécommande (exécutez la méthode next () du générateur).Le générateur produit la quantité d'électricité requise (renvoie une certaine valeur par le rendement) et passe à nouveau en mode veille (la fonction générateur attend le prochain appel de l'itérateur). La boucle continue tant que le générateur peut produire de l'électricité (il y a des déclarations de rendement) ou elle ne s'arrêtera pas (le retour est rencontré dans la fonction générateur).
Et dans toute cette analogie, le point clé est le panneau de contrôle (itérateur). Il peut être transmis à différentes parties de l'application et, au bon moment, «prendre» des valeurs du générateur. Pour compléter l'image, vous pouvez ajouter un nombre illimité de boutons sur le panneau de contrôle pour démarrer le générateur dans certains modes (en passant des paramètres à la méthode suivante (tous les paramètres) de l'itérateur), mais deux boutons suffisent pour implémenter le hook.
Option 4. Générateur sans promesses
Cette option est fournie pour plus de clarté, car à pleine capacité, les générateurs fonctionnent en conjonction avec des promesses (mécanisme async / wait). Mais cette option fonctionne et a le droit d'exister dans certaines situations simples.
Je crée une variable dans le crochet pour stocker la référence à l'itérateur (cellule pour le panneau de contrôle du générateur)
const iteratorRef = useRef(null);
. . , next() ( next). :
const updateCounter = () => {
iteratorRef.current.next();
};
const checkImageLoading = (url) => {
const imageChecker = new Image();
imageChecker.addEventListener("load", updateCounter);
imageChecker.addEventListener("error", updateCounter);
imageChecker.src = url;
};
. , , , next . , " ". dispatch , . :
function* main() {
for (let i = 0; i < imgArray.length; i++) {
checkImageLoading(imgArray[i].src);
}
for (let i = 0; i < imgArray.length; i++) {
yield true;
dispatch({
type: ACTIONS.SET_COUNTER,
data: stateRef.current.counter + stateRef.current.counterStep
});
}
}
"" , ( iteratorRef. ( next ).
.
import { useReducer, useEffect, useLayoutEffect, useRef } from "react";
import { reducer, initialState, ACTIONS } from "./state";
const PRELOADER_SELECTOR = ".preloader__wrapper";
const PRELOADER_COUNTER_SELECTOR = ".preloader__counter";
const usePreloader = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const stateRef = useRef(state);
const iteratorRef = useRef(null);
const preloaderEl = document.querySelector(PRELOADER_SELECTOR);
const counterEl = document.querySelector(PRELOADER_COUNTER_SELECTOR);
const updateCounter = () => {
iteratorRef.current.next();
};
const checkImageLoading = (url) => {
const imageChecker = new Image();
imageChecker.addEventListener("load", updateCounter);
imageChecker.addEventListener("error", updateCounter);
imageChecker.src = url;
};
useEffect(() => {
const imgArray = document.querySelectorAll("img");
if (imgArray.length > 0) {
dispatch({
type: ACTIONS.SET_COUNTER_STEP,
data: Math.floor(100 / imgArray.length) + 1
});
}
function* main() {
for (let i = 0; i < imgArray.length; i++) {
checkImageLoading(imgArray[i].src);
}
for (let i = 0; i < imgArray.length; i++) {
yield true;
dispatch({
type: ACTIONS.SET_COUNTER,
data: stateRef.current.counter + stateRef.current.counterStep
});
}
}
iteratorRef.current = main();
iteratorRef.current.next();
}, []);
useLayoutEffect(() => {
stateRef.current = state;
if (counterEl) {
stateRef.current.counter < 100
? (counterEl.innerHTML = `${stateRef.current.counter}%`)
: hidePreloader(preloaderEl);
}
}, [state]);
return;
};
const hidePreloader = (preloaderEl) => {
preloaderEl.remove();
};
export default usePreloader;
.
5.
. next ( ). ( ).
:
const getImageLoading = async function* (imagesArray) {
for (const img of imagesArray) {
yield new Promise((resolve, reject) => {
const imageChecker = new Image();
imageChecker.addEventListener("load", () => resolve(true));
imageChecker.addEventListener("error", () => resolve(true));
imageChecker.src = img.url;
});
}
};
:
for await (const response of getImageLoading(imgArray)) {
dispatch({
type: ACTIONS.SET_COUNTER,
data: stateRef.current.counter + stateRef.current.counterStep
});
}
for await ... of. Next.
- . , , .
import { useReducer, useEffect, useRef } from "react";
import { reducer, initialState, ACTIONS } from "./state";
const PRELOADER_SELECTOR = ".preloader__wrapper";
const PRELOADER_COUNTER_SELECTOR = ".preloader__counter";
const usePreloader = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const stateRef = useRef(state);
const preloaderEl = document.querySelector(PRELOADER_SELECTOR);
const counterEl = document.querySelector(PRELOADER_COUNTER_SELECTOR);
useEffect(() => {
async function imageLoading() {
const imgArray = document.querySelectorAll("img");
if (imgArray.length > 0) {
dispatch({
type: ACTIONS.SET_COUNTER_STEP,
data: Math.floor(100 / imgArray.length) + 1
});
for await (const response of getImageLoading(imgArray)) {
dispatch({
type: ACTIONS.SET_COUNTER,
data: stateRef.current.counter + stateRef.current.counterStep
});
}
}
}
imageLoading();
}, []);
useEffect(() => {
stateRef.current = state;
if (counterEl) {
stateRef.current.counter < 100
? (counterEl.innerHTML = `${stateRef.current.counter}%`)
: hidePreloader(preloaderEl);
}
}, [state]);
return;
};
const getImageLoading = async function* (imagesArray) {
for (const img of imagesArray) {
yield new Promise((resolve, reject) => {
const imageChecker = new Image();
imageChecker.addEventListener("load", () => resolve(true));
imageChecker.addEventListener("error", () => resolve(true));
imageChecker.src = img.url;
});
}
};
const hidePreloader = (preloaderEl) => {
preloaderEl.remove();
};
export default usePreloader;
:
:
useRef ( )
comment contrôler le flux des événements à l'aide de générateurs, mais sans utiliser de promesses (à l'aide de rappels)
comment contrôler le flux des événements avec des gestionnaires promis à l'aide de générateurs et d'une boucle for await ... of
Lien sandbox
Lien vers le référentiel
A suivre ... redux-saga ...