如何使我的react应用程序仅在昂贵的计算完成时渲染?

krugob8w  于 2021-09-23  发布在  Java
关注(0)|答案(1)|浏览(323)

我是初学者,恐怕我完全糊涂了。
做一个记忆游戏练习,我想从(比如)40个文件夹中随机选择一个3x3的图像网格,当单击一个图像时,随机生成一组9个新的图像 clickCount 在游戏状态下(目标是点击尽可能多的未点击的图片)。也许问题在于我的thinkpad速度慢,但返回带有URL的对象数组的函数耗时太长,以致应用程序可能崩溃(随机数组未就绪)或随机跳过加载时的图像函数调用(希望在添加记分板逻辑之前解决此问题,如下所示)。
我只是从react开始,所以我一直在梳理 suspense , memo 、延迟加载、承诺/异步等。但在我学习的早期阶段,我很难充分理解哪一个适合这种情况。
imageloader.js

// Import all images (Webpack)
const importAll = (r) => {
  let images = [];
  r.keys().forEach((item, i) => {
    images.push({
      ...r(item),
      id: `img${i}`,
      clickCount: 0,
    });
  });
  return images;
};
const imageExport = importAll(require.context("./images", false, /\.jpg$/));

export { imageExport };

game.js

import React, { useState, useEffect, useCallback } from "react";
import ImageElement from "./ImageElement";
import { imageExport } from "../imageLoader";
import "../styles/Game.css";

// SET GRID SIZE (no. of tiles per length of square)
const gridSize = 3;

const Game = () => {
  const [gameState, setGameState] = useState(imageExport);
  const [isLoaded, setIsLoaded] = useState(0);
  const [tileSet, setTileSet] = useState(null);
  const gridCount = gridSize**2;

  // function to return random tile array
  const randomiseTiles = useCallback(
    (gridCount) => {
      const imgQuantity = Object.keys(gameState).length;
      const getRandomIndex = () => Math.floor(Math.random() * imgQuantity);
      let imgIndexes = [];

      for (let i = 0; i < gridCount; i++) {
        const newRandomIndex = getRandomIndex();
        if (imgIndexes.some((index) => index === newRandomIndex)) {
          // is duplicate, try again
          i--;
        } else {
          imgIndexes.push(newRandomIndex);
        }
      }

      // FIX: successive high IDs crash the app as the array isn't ready in time
      const imgSet = imgIndexes.map((id) => gameState[id]);

      if (!imgSet.some((img) => img.clickCount !== 0)) {
         // add logic later to guarantee at least one img is always unclicked
      }
      return imgSet;
    },
    [gameState]
  );

  // setTileSet after initial render to keep useState immutable
  useEffect(() => {
    if (!tileSet) {
      setTileSet(randomiseTiles(gridCount));
    }
  }, []);

  // report loading
  const reportLoaded = () => {
    setIsLoaded((prevIsLoaded) => {
      if (prevIsLoaded < gridCount) {
        return prevIsLoaded + 1;
      };
      // FIX: Is there a better way to reset isLoaded?
      return 1;
    });
  };

  // generate new tile set on image click
  useEffect(() => {
    setTileSet(randomiseTiles(gridCount));
  }, [gameState, randomiseTiles, gridCount]);

  // update game state on click
  const updateGameState = (id) => {
    const index = Number(id);
    setGameState((prevGameState) => {
      const newGameState = [...prevGameState];
      newGameState[index] = {
        ...prevGameState[index],
        clickCount: prevGameState[index].clickCount + 1,
      };
      return newGameState;
    });
  };

  const gameStyle = isLoaded === gridCount ? {
    display: "grid",
    gridTemplateColumns: `repeat(${gridSize}, 1fr)`,
    gridTemplateRows: `repeat(${gridSize}, 1fr)`,
  } : {
    display: "none"
  };

  return !tileSet ? null : (
    <>
      {isLoaded < gridCount && <div>Replace with loader animation etc.</div>}
      <div id="game" style={gameStyle}>
        {/* FIX: Sometimes renders before tileSet is ready /*}
        {tileSet.map((img) => {
          return (
            <ImageElement
              key={img.id}
              src={img.default}
              imgId={img.id}
              reportLoaded={reportLoaded}
              reportClick={updateGameState}
            />
          );
        })}
      </div>
    </>
  );
};

export default Game;

imageelement.js

import React from "react";

// pure component
export default const ImageElement = ({ src, reportLoaded, reportClick, imgId }) => {
  return (
    <img
      key={`image-${imgId}`}
      src={src}
      alt="image-alt-text"
      {/* I randomly get between 5-9 triggers of reportLoaded on each click
          even when all images load on the screen correctly
      */}
      onLoad={reportLoaded}
      onClick={() => reportClick(imgId)};
    />
  );
};

问我任何你想问的问题如果我不清楚,我只是完全停留在这一点上,并将感谢一些意见(对概述的问题或任何其他我做了错事)。
非常感谢。

9cbw7uwe

9cbw7uwe1#

你正在做一些效率低下的事情。 array.some() 每次查看数组1个元素,直到找到匹配项或到达数组末尾。做一次很好,但你正在做 gridCount 时间开始累积。而不是 array.some() ,您可以创建一个对象来跟踪使用了哪些索引:

let usedIndexes = {};
for (let i = 0; i < gridCount; i++) {
    const newRandomIndex = getRandomIndex();

    //if (imgIndexes.some((index) => index === newRandomIndex)) { //this is slow

    if (usedIndexes[newRandomIndex] === true) { //this is really fast
      // is duplicate, try again
      i--;
    } else {
      imgIndexes.push(newRandomIndex);

      usedIndexes[newRandomIndex] = true;
    }
  }

另一个低效的做法是,您选择的随机索引比实际需要的要多,因为您允许它选择已经使用过的索引,然后将它们丢弃。以下是如何只选择所需的索引数:

//prefill imgChoices with all the unused indexes:
let imgChoices = [];
for(let i = 0; i < imgQuantity; i++) {
    imgChoices.push(i);
}

//not going to use this anymore:
//const getRandomIndex = () => Math.floor(Math.random() * imgQuantity);

for(let j = 0; j < gridCount; j++) {
    //imgChoices.length will decrease by 1 each iteration as indexes are used up:
    const newRandomNumber = Math.floor(Math.random() * imgChoices.length);

    //imgChoices only contains unused indexes, so we can take an index from
    // it and we don't have to check if it's used:
    const newRandomIndex = imgChoices[newRandomNumber];
    imgIndexes.push(newRandomIndex);

    if(newRandomNumber < imgChoices.length - 1) {
        //if we didn't pick the last element, replace the index we chose
        // with the one at the end and eliminate the end element:
        imgChoices[newRandomNumber] = imgChoices.pop();
    }
    else {
        //if we picked the last element, just eliminate it:
        imgChoices.pop();
    }
}

上面的代码就是选择索引所需的全部代码。看看表演会发生什么。

相关问题