javascript - 如何让我的 React 应用程序仅在完成昂贵的计算时才呈现?
问题描述
这里的初学者,恐怕我完全糊涂了。
做一个记忆游戏练习,我想从(比如说)40 个文件夹中随机选择一个 3x3 的图像网格,当点击一个图像时会渲染一组新的随机 9 个图像,以更新clickCount
游戏状态(目标是单击尽可能多的未单击图像)。也许问题是我的 ThinkPad 速度慢,但是返回带有 url 的对象数组的函数需要很长时间,以至于应用程序可能崩溃(随机数组未准备好)或随机跳过图像加载函数调用(想在我添加之前解决这个问题记分牌逻辑,以下缺失)。
我实际上只是从 React 开始,所以我一直在梳理suspense
, memo
, 延迟加载,promise/async 等,但在我学习知道哪个适合这种情况的早期阶段,我很难理解.
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 };
游戏.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)};
/>
);
};
如果我不清楚,可以问我任何你想问的问题,我只是完全停留在这一点上,希望能提供一些意见(关于概述的问题或我做错的任何其他事情)。
谢谢!
解决方案
你正在做一些低效的事情。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();
}
}
上面的代码就是您选择索引所需的全部内容。看看表演会发生什么。
推荐阅读
- jira - 在 EazyBi 中,如何做维度名称的子字符串
- snowflake-cloud-data-platform - TSQL 翻译成雪花
- lazy-evaluation - DM-script 有惰性求值吗?
- scala - 在 Scala 中获取 PrepareStament 变量
- flutter - 从真实的移动浏览器访问flutter localhost
- javascript - chrome.storage.local.get 在 chrome.storage.sync.get 回调中失败
- loopbackjs - 如何使用带有 Loopback 4 mongodb 连接器的 createIndex 进行地理位置存储?
- python - 单击按钮时从滚动区域中删除拖拽的复选框
- python - 为什么我不能用我的 python 脚本打开这个特定的可执行文件,是否有解决方法?
- amazon-ecs - 如何调试因弹性负载均衡器健康检查不正常而偶尔重启任务的 ECS Fargate 服务