Sobre geradores
Geradores são um novo tipo de função que foi introduzido no ES6. Muitos artigos foram escritos sobre eles e muitos exemplos teóricos são dados. Quanto a mim, o livro You don't know JS , part of async & performance , ajudou a esclarecer a essência dos geradores e como usá-los . De todos os livros de JS que estudei, este é o mais repleto de informações úteis sem água.
Imagine que o gerador (a função na declaração, que é *) seja algum tipo de dispositivo elétrico com um painel de controle remoto. Depois de criar e montar o gerador (declaração da função), você precisa "girá-lo" (executar esta função) para que ele gire em marcha lenta e "alimente" o painel de controle com ele mesmo (quando a função do gerador é executada, ele retorna um iterador). Este controle remoto tem dois botões: Start (chamar o próximo método do iterador pela primeira vez) e Next (chamadas subsequentes para o próximo método do iterador). Então, com este painel de controle, você pode correr por toda a usina (de acordo com nossa aplicação) e quando precisar de energia elétrica (alguns valores da função do gerador), pressione o próximo botão no controle remoto (execute o método next () do gerador).O gerador produz a quantidade necessária de eletricidade (retorna um certo valor por meio do rendimento) e entra no modo ocioso novamente (a função do gerador aguarda a próxima chamada do iterador). O loop continua enquanto o gerador puder produzir eletricidade (existem declarações de rendimento) ou não irá parar (o retorno é encontrado na função do gerador).
E em toda essa analogia, o ponto chave é o painel de controle (iterador). Ele pode ser passado para diferentes partes da aplicação e no momento certo "pegar" valores do gerador. Para completar a imagem, você pode adicionar um número ilimitado de botões no painel de controle para iniciar o gerador em certos modos (passando parâmetros para o próximo método (quaisquer parâmetros) do iterador), mas dois botões são suficientes para implementar o gancho.
Opção 4. Gerador sem promessas
Esta opção é fornecida para maior clareza, porque geradores funcionam com força total com promessas (mecanismo assíncrono / espera). Mas esta opção está funcionando e tem o direito de existir em certas situações simples.
Eu crio uma variável no gancho para armazenar a referência ao iterador (célula para o painel de controle do gerador)
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 ( )
como gerenciar o fluxo de eventos usando geradores, mas sem usar promessas (usando callbacks)
como gerenciar o fluxo de eventos com manipuladores prometidos usando geradores e um loop for await ... of
Link da sandbox
Link do repositório
Continua ... redux-saga ...