React(ジェネレーター、sagas、rxjs)パート2の小さなカスタムフックに関する壮大な物語

パート1。カスタムフック





パート3。Redux-saga





ジェネレーターについて

ジェネレータは、ES6で導入された新しい種類の機能です。それらについて多くの記事が書かれ、多くの理論的な例が示されています。私の場合、非同期とパフォーマンスの一部である「あなたはJSを知らない」という本は、ジェネレーターの本質とその使用方法を明確にするのに役立ちました私が研究したすべてのJSの本の中で、これは水なしで役立つ情報が最も詰まっています。





ジェネレーター(宣言内の関数、つまり*)が、リモートコントロールパネルを備えたある種の電気デバイスであると想像してみましょう。ジェネレーターを作成してマウントした後(関数宣言)、アイドル速度で回転し、コントロールパネル自体に「フィード」するように「スピン」(この関数を実行)する必要があります(ジェネレーター関数が実行されると、イテレーターが返されます)。このリモートコントロールには、開始(イテレーターの次のメソッドを初めて呼び出す)と次へ(イテレーターの次のメソッドへの後続の呼び出し)の2つのボタンがあります。次に、このコントロールパネルを使用して、(アプリケーションに応じて)発電所全体を駆け回ることができ、電気エネルギー(発電機機能からのいくつかの値)が必要な場合は、リモートコントロールの次のボタンを押します(発電機の次の()メソッドを実行します)。ジェネレーターは必要な量の電力を生成し(yieldを通じて特定の値を返します)、再びアイドルモードになります(ジェネレーター関数はイテレーターからの次の呼び出しを待ちます)。ループは、発電機が発電できる限り(yieldステートメントがあります)、または停止しない限り(発電機機能で戻りが発生する)継続します。





そして、この全体的なアナロジーでは、重要なポイントはコントロールパネル(イテレーター)です。アプリケーションのさまざまな部分に渡すことができ、適切なタイミングでジェネレーターから値を「取得」します。全体像を完成させるために、コントロールパネルに無制限の数のボタンを追加して、特定のモードでジェネレーターを起動できます(イテレーターの次のメソッド(任意のパラメーター)にパラメーターを渡す)が、フックを実装するには2つのボタンで十分です。





オプション4.約束のないジェネレータ

このオプションはわかりやすくするために提供されています。ジェネレーターは、promise(非同期/待機メカニズム)で完全に機能します。しかし、このオプションは機能しており、特定の単純な状況で存在する権利があります。





イテレーター(ジェネレーターコントロールパネルのセル)への参照を格納する変数をフックに作成します





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





  • ジェネレータを使用して、ただしPromiseを使用せずに(コールバックを使用して)イベントのフローを制御する方法





  • ジェネレーターとループの待機...を使用して、約束されたハンドラーでイベントのフローを制御する方法





サンドボックスリンク 





リポジトリリンク 









続く... redux-saga..。












All Articles