Uma saga épica sobre um pequeno gancho personalizado para React (geradores, sagas, rxjs) parte 2

Parte 1. Gancho personalizado





Parte 3. Redux-saga





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 ...












All Articles