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

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

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

  1. // Import all images (Webpack)
  2. const importAll = (r) => {
  3. let images = [];
  4. r.keys().forEach((item, i) => {
  5. images.push({
  6. ...r(item),
  7. id: `img${i}`,
  8. clickCount: 0,
  9. });
  10. });
  11. return images;
  12. };
  13. const imageExport = importAll(require.context("./images", false, /\.jpg$/));
  14. export { imageExport };

game.js

  1. import React, { useState, useEffect, useCallback } from "react";
  2. import ImageElement from "./ImageElement";
  3. import { imageExport } from "../imageLoader";
  4. import "../styles/Game.css";
  5. // SET GRID SIZE (no. of tiles per length of square)
  6. const gridSize = 3;
  7. const Game = () => {
  8. const [gameState, setGameState] = useState(imageExport);
  9. const [isLoaded, setIsLoaded] = useState(0);
  10. const [tileSet, setTileSet] = useState(null);
  11. const gridCount = gridSize**2;
  12. // function to return random tile array
  13. const randomiseTiles = useCallback(
  14. (gridCount) => {
  15. const imgQuantity = Object.keys(gameState).length;
  16. const getRandomIndex = () => Math.floor(Math.random() * imgQuantity);
  17. let imgIndexes = [];
  18. for (let i = 0; i < gridCount; i++) {
  19. const newRandomIndex = getRandomIndex();
  20. if (imgIndexes.some((index) => index === newRandomIndex)) {
  21. // is duplicate, try again
  22. i--;
  23. } else {
  24. imgIndexes.push(newRandomIndex);
  25. }
  26. }
  27. // FIX: successive high IDs crash the app as the array isn't ready in time
  28. const imgSet = imgIndexes.map((id) => gameState[id]);
  29. if (!imgSet.some((img) => img.clickCount !== 0)) {
  30. // add logic later to guarantee at least one img is always unclicked
  31. }
  32. return imgSet;
  33. },
  34. [gameState]
  35. );
  36. // setTileSet after initial render to keep useState immutable
  37. useEffect(() => {
  38. if (!tileSet) {
  39. setTileSet(randomiseTiles(gridCount));
  40. }
  41. }, []);
  42. // report loading
  43. const reportLoaded = () => {
  44. setIsLoaded((prevIsLoaded) => {
  45. if (prevIsLoaded < gridCount) {
  46. return prevIsLoaded + 1;
  47. };
  48. // FIX: Is there a better way to reset isLoaded?
  49. return 1;
  50. });
  51. };
  52. // generate new tile set on image click
  53. useEffect(() => {
  54. setTileSet(randomiseTiles(gridCount));
  55. }, [gameState, randomiseTiles, gridCount]);
  56. // update game state on click
  57. const updateGameState = (id) => {
  58. const index = Number(id);
  59. setGameState((prevGameState) => {
  60. const newGameState = [...prevGameState];
  61. newGameState[index] = {
  62. ...prevGameState[index],
  63. clickCount: prevGameState[index].clickCount + 1,
  64. };
  65. return newGameState;
  66. });
  67. };
  68. const gameStyle = isLoaded === gridCount ? {
  69. display: "grid",
  70. gridTemplateColumns: `repeat(${gridSize}, 1fr)`,
  71. gridTemplateRows: `repeat(${gridSize}, 1fr)`,
  72. } : {
  73. display: "none"
  74. };
  75. return !tileSet ? null : (
  76. <>
  77. {isLoaded < gridCount && <div>Replace with loader animation etc.</div>}
  78. <div id="game" style={gameStyle}>
  79. {/* FIX: Sometimes renders before tileSet is ready /*}
  80. {tileSet.map((img) => {
  81. return (
  82. <ImageElement
  83. key={img.id}
  84. src={img.default}
  85. imgId={img.id}
  86. reportLoaded={reportLoaded}
  87. reportClick={updateGameState}
  88. />
  89. );
  90. })}
  91. </div>
  92. </>
  93. );
  94. };
  95. export default Game;

imageelement.js

  1. import React from "react";
  2. // pure component
  3. export default const ImageElement = ({ src, reportLoaded, reportClick, imgId }) => {
  4. return (
  5. <img
  6. key={`image-${imgId}`}
  7. src={src}
  8. alt="image-alt-text"
  9. {/* I randomly get between 5-9 triggers of reportLoaded on each click
  10. even when all images load on the screen correctly
  11. */}
  12. onLoad={reportLoaded}
  13. onClick={() => reportClick(imgId)};
  14. />
  15. );
  16. };

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

9cbw7uwe

9cbw7uwe1#

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

  1. let usedIndexes = {};
  2. for (let i = 0; i < gridCount; i++) {
  3. const newRandomIndex = getRandomIndex();
  4. //if (imgIndexes.some((index) => index === newRandomIndex)) { //this is slow
  5. if (usedIndexes[newRandomIndex] === true) { //this is really fast
  6. // is duplicate, try again
  7. i--;
  8. } else {
  9. imgIndexes.push(newRandomIndex);
  10. usedIndexes[newRandomIndex] = true;
  11. }
  12. }

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

  1. //prefill imgChoices with all the unused indexes:
  2. let imgChoices = [];
  3. for(let i = 0; i < imgQuantity; i++) {
  4. imgChoices.push(i);
  5. }
  6. //not going to use this anymore:
  7. //const getRandomIndex = () => Math.floor(Math.random() * imgQuantity);
  8. for(let j = 0; j < gridCount; j++) {
  9. //imgChoices.length will decrease by 1 each iteration as indexes are used up:
  10. const newRandomNumber = Math.floor(Math.random() * imgChoices.length);
  11. //imgChoices only contains unused indexes, so we can take an index from
  12. // it and we don't have to check if it's used:
  13. const newRandomIndex = imgChoices[newRandomNumber];
  14. imgIndexes.push(newRandomIndex);
  15. if(newRandomNumber < imgChoices.length - 1) {
  16. //if we didn't pick the last element, replace the index we chose
  17. // with the one at the end and eliminate the end element:
  18. imgChoices[newRandomNumber] = imgChoices.pop();
  19. }
  20. else {
  21. //if we picked the last element, just eliminate it:
  22. imgChoices.pop();
  23. }
  24. }

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

展开查看全部

相关问题