Kisah epik tentang hook kustom kecil untuk React (generator, sagas, rxjs) bagian 2

Bagian 1. Hook Kustom





Bagian 3. Redux-saga





Tentang generator

Generator adalah jenis fungsi baru yang diperkenalkan di ES6. Banyak artikel telah ditulis tentang mereka dan banyak contoh teoritis diberikan. Bagi saya, buku You don't know JS , bagian dari async & performance , membantu menjelaskan esensi generator dan bagaimana menggunakannya . Dari semua buku JS yang pernah saya pelajari, buku ini yang paling berisi informasi berguna tanpa air.





Mari kita bayangkan generator (fungsi dalam deklarasi, yaitu *) adalah sejenis perangkat listrik dengan panel kendali jarak jauh. Setelah membuat dan memasang generator (deklarasi fungsi), Anda perlu "memutarnya" (menjalankan fungsi ini) sehingga ia berputar pada kecepatan idle dan "memberi makan" panel kontrol dengan sendirinya (ketika fungsi generator dijalankan, ia mengembalikan iterator). Remote control ini memiliki dua tombol: Mulai (panggil metode iterator berikutnya untuk pertama kalinya) dan Berikutnya (panggilan berikutnya ke metode iterator berikutnya). Kemudian, dengan panel kontrol ini, Anda dapat bergegas ke seluruh pembangkit listrik (sesuai dengan aplikasi kami) dan ketika Anda membutuhkan energi listrik (beberapa nilai dari fungsi generator), tekan tombol berikutnya pada remote control (jalankan metode next () generator).Generator menghasilkan jumlah listrik yang diperlukan (mengembalikan nilai tertentu melalui hasil) dan beralih ke mode diam lagi (fungsi generator menunggu panggilan berikutnya dari iterator). Loop terus berlanjut selama generator dapat menghasilkan listrik (ada pernyataan yield) atau tidak akan berhenti (kembali dijumpai dalam fungsi generator).





Dan dalam keseluruhan analogi ini, poin kuncinya adalah panel kontrol (iterator). Ini dapat diteruskan ke berbagai bagian aplikasi dan pada saat yang tepat "mengambil" nilai dari generator. Untuk melengkapi gambar, Anda dapat menambahkan tombol dalam jumlah tak terbatas pada panel kontrol untuk memulai generator dalam mode tertentu (meneruskan parameter ke metode berikutnya (parameter apa pun) dari iterator), tetapi dua tombol sudah cukup untuk mengimplementasikan pengait.





Opsi 4. Generator tanpa janji

Opsi ini disediakan untuk kejelasan, karena generator bekerja dengan kekuatan penuh dengan promise (mekanisme async / await). Tetapi opsi ini berfungsi dan berhak untuk hidup dalam situasi sederhana tertentu.





Saya membuat variabel di hook untuk menyimpan referensi ke iterator (sel untuk panel kontrol generator)





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 ( )





  • cara mengontrol aliran peristiwa menggunakan generator, tetapi tanpa menggunakan promise (menggunakan callback)





  • cara mengontrol aliran peristiwa dengan penangan yang dijanjikan menggunakan generator dan menunggu ... of loop





Tautan kotak  pasir





Link  repositori









Bersambung ... redux-saga ...












All Articles