Teil 1. Benutzerdefinierter Haken
Über Generatoren
Generatoren sind eine neue Art von Funktion, die in ES6 eingeführt wurde. Es wurden viele Artikel darüber geschrieben und viele theoretische Beispiele gegeben. Was mich betrifft , hat das Buch Sie kennen JS nicht , das Teil von Async & Performance ist , dazu beigetragen, die Essenz von Generatoren und deren Verwendung zu klären . Von allen JS-Büchern, die ich studiert habe, ist dieses das nützlichste mit nützlichen Informationen ohne Wasser.
Stellen Sie sich vor, der Generator (die Funktion in der Deklaration, die * ist) ist eine Art elektrisches Gerät mit einer Fernbedienung. Nach dem Erstellen und Mounten des Generators (Funktionsdeklaration) müssen Sie ihn "drehen" (diese Funktion ausführen), damit er sich im Leerlauf dreht und das Bedienfeld von selbst "speist" (wenn die Generatorfunktion ausgeführt wird, wird ein Iterator zurückgegeben). Dieses Bedienfeld verfügt über zwei Schaltflächen: Start (zum ersten Mal die nächste Methode des Iterators aufrufen) und Next (nachfolgende Aufrufe zur nächsten Methode des Iterators). Mit diesem Bedienfeld können Sie dann (gemäß unserer Anwendung) durch das gesamte Kraftwerk rasen. Wenn Sie elektrische Energie benötigen (einige Werte aus der Generatorfunktion), drücken Sie die nächste Taste auf der Fernbedienung (führen Sie die next () -Methode des Generators aus).Der Generator erzeugt die erforderliche Strommenge (gibt durch Ausbeute einen bestimmten Wert zurück) und wechselt wieder in den Leerlaufmodus (die Generatorfunktion wartet auf den nächsten Aufruf des Iterators). Die Schleife wird fortgesetzt, solange der Generator Strom erzeugen kann (es gibt Ertragsangaben) oder nicht stoppt (in der Generatorfunktion tritt eine Rückkehr auf).
In dieser ganzen Analogie ist der zentrale Punkt das Bedienfeld (Iterator). Es kann an verschiedene Teile der Anwendung übergeben werden und zum richtigen Zeitpunkt Werte vom Generator "übernehmen". Um das Bild zu vervollständigen, können Sie eine unbegrenzte Anzahl von Schaltflächen auf dem Bedienfeld hinzufügen, um den Generator in bestimmten Modi zu starten (Übergabe von Parametern an die nächste Methode (beliebige Parameter) des Iterators). Es reichen jedoch zwei Schaltflächen aus, um den Hook zu implementieren.
Option 4. Generator ohne Versprechen
Diese Option dient der Übersichtlichkeit, da Generatoren arbeiten in voller Kraft mit Versprechungen (Async / Wait-Mechanismus). Diese Option funktioniert jedoch und hat das Recht, in bestimmten einfachen Situationen zu existieren.
Ich erstelle eine Variable im Hook, um den Verweis auf den Iterator zu speichern (Zelle für das Generator-Bedienfeld).
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 ( )
wie man den Ereignisfluss mit Generatoren steuert, aber ohne Versprechen (mit Rückrufen)
Wie man den Ereignisfluss mit vielversprechenden Handlern mithilfe von Generatoren und einer erwarteten Schleife verwaltet
Sandbox- Link
Repository- Link
Fortsetzung folgt ... Redux-Saga ...