reactjs - 我的 React 组件在 props 数据可用之前渲染,使用钩子
问题描述
所以我正在创建一个简单的瓷砖匹配应用程序。我有一个 tile 组件,该组件从包含该 tile 的游戏板组件中获取道具,并且该组件本身位于应用程序中,显然具有主要逻辑和状态数据。
我需要每个 Tile 都有自己的逻辑来知道它是否应该翻转。
我遇到的问题是,当我创建一个状态数据时,瓷砖被翻转和/或应该保持翻转,瓷砖正在使用以前的渲染数据运行它们的组件级逻辑。
这意味着我匹配两个图块,它们被添加到相关状态 - selectedTiles 和matchedTiles 被传递给所有图块。瓦片检查 selectedTiles 中是否有两个值,如果有,则检查 matchTiles 以查看该值是否与组件级别的 prop 匹配。但是,由 selectedTiles 更改触发的检查matchedTiles 的逻辑在matchedTiles 道具到达组件之前运行。
我知道这是因为状态快照,但我不确定如何实际解决问题。
对任何误解或解释表示歉意,因为这是我第一个完全自行编写的 react 应用程序。
我在下面的 git 上链接了存储库。
解决方案
我拉了你的回购并稍微更改了代码。
在我看来,最好使用 useState 保存 2 个单独的变量,一个用于打开的图块,一个用于第二个“猜测”。
我还添加了一个 setTimeout 以在 1 秒后隐藏两个打开的图块,以防它们不匹配。
我添加了一个 'awaitingFlip' 变量,以防止用户在最后 2 个瓷砖没有因错误猜测而被翻转回来时翻转更多瓷砖。
传递每个图块中的所有信息是错误的编码。应用程序变慢了,因为 react 必须计算更多的东西。我为每个图块添加了一个道具“isFlipped”,如果两个图块已匹配或一个图块已被检查为第一次或第二次猜测,这是正确的。
我还为每个图块添加了一个“tileId”属性,因为我不喜欢将索引用作 id。
我将 unsplash 搜索从“南非”更改为“巴塔哥尼亚”,因为我得到了令人沮丧的重复图像。我建议你从 repo 中隐藏你的 unsplash api 密钥并生成一个新的,因为人们将能够使用它。
您应该能够完成其余的工作(例如“再次播放”、“重新洗牌”等...)。
我希望我有所帮助。如果您需要更多帮助,请发表评论。
这是代码(我不包括的文件是相同的):
应用程序.js:
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import unsplash from "../api/unsplash";
import Header from "./Header";
import GameBoard from "./GameBoard";
function App() {
const [searchTerm, setSearchTerm] = useState("patagonia");
const [images, setImages] = useState([]);
const [randomisedImages, setRandomisedImages] = useState([]);
useEffect(() => {
randomiseImages(images);
}, [images]);
useEffect(() => {
getImages();
}, [searchTerm]);
const generateTileId = () => {
return 'tile_id_' + Math.random().toString().substr(2, 8);
}
const getImages = async () => {
const response = await unsplash.get("/search/photos", {
params: { query: searchTerm },
});
setImages(response.data.results);
};
const randomiseImages = (images) => {
let randomizedImages = [...images, ...images];
var m = images.length,
t,
i;
while (m) {
i = Math.floor(Math.random() * m--);
t = randomizedImages[m];
randomizedImages[m] = randomizedImages[i];
randomizedImages[i] = t;
}
let finalArray = [];
for (let x of randomizedImages) {
finalArray.push({
...x,
tileId: generateTileId()
})
}
setRandomisedImages([...finalArray]);
};
return (
<div>
<Container>
<Header />
<Main>
<GameBoard images={randomisedImages} />
</Main>
</Container>
</div>
);
}
export default App;
const Container = styled.div`
width: 100%;
height: 100vh;
display: grid;
grid-template-rows: 7rem;
`;
const Main = styled.div`
display: grid;
grid-template-columns: auto;
`;
游戏板.js:
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import Tile from "./Tile";
function GameBoard({ images }) {
const [selectedTile, setSelectedTile] = useState(null);
const [secondTile, setSecondTile] = useState(null);
const [matchedTiles, setMatchedTiles] = useState([]);
const [awaitingFlip, setAwaitingFlip] = useState(false);
useEffect(() => {
if (matchedTiles.length > 0 && matchedTiles.length === images.length / 2) {
alert('YOU WON!!! :)');
}
}, [matchedTiles])
const onTileClick = (tileId, id) => {
if (!matchedTiles.includes(id) && tileId !== selectedTile && !awaitingFlip) {
let subjectId = images.find(x => x.tileId === selectedTile)?.id;
if (!selectedTile) {
setSelectedTile(tileId);
} else {
if (id === subjectId) {
setMatchedTiles([...matchedTiles, id]);
setSelectedTile(null);
} else {
setSecondTile(tileId);
setAwaitingFlip(true);
setTimeout(() => {
setSecondTile(null);
setSelectedTile(null);
setAwaitingFlip(false);
}, 1000);
}
}
}
}
return (
<TileContainer>
{images.length > 1 &&
images.map(({ description, urls, id, tileId }, index) => (
<Tile
key={index}
id={id}
tileId={tileId}
alt={description}
src={urls.regular}
onTileClick={onTileClick}
isFlipped={selectedTile === tileId || secondTile === tileId || matchedTiles.includes(id)}
/>
))}
</TileContainer>
);
}
export default GameBoard;
const TileContainer = styled.div`
background: black;
padding: 7rem;
display: flex;
justify-content: space-evenly;
align-items: center;
flex-wrap: wrap;
`;
Tile.js:
import React, { useState, useEffect, useLayoutEffect } from "react";
import tile from "./tile.jpeg";
import styled from "styled-components";
function Tile({ id, alt, src, onTileClick, tileId, isFlipped }) {
return (
<Imgcontainer onClick={() => onTileClick(tileId, id)}>
<img alt={alt} src={isFlipped ? src : tile} id={id} />
</Imgcontainer>
);
}
export default Tile;
const Imgcontainer = styled.div`
height: 10rem;
width: 10rem;
margin: 1rem;
border: 2px solid white;
border-radius: 10%;
cursor: pointer;
img {
width: 100%;
height: 100%;
border-radius: 10%;
}
img:hover {
transform: scale(1.1);
box-shadow: 0 8px 15px #dcfffe;
border: 2px solid white;
border-radius: 10%;
}
`;
推荐阅读
- json - 如何在一个批次中发送多个 post 请求?
- python - 从 windows 和 linux 的子文件夹导入函数
- javascript - 执行上下文:为什么这个 javascript 代码会有这样的行为?
- php - 无法使用 curl 命令在 php 中解码 json 数据
- sql - 如何替换sql中的约束?
- java - 使用 Jetty 9.4.19 v20190610 设置 Vaadin 10+
- r - 如何在 Rstudio 中读取多个 .txt 文件并制作数据框?
- ios - 全新安装后领域触发迁移异常
- python - Pandas - 如何通过逗号分隔的字符串进行分隔和分组
- android - 启用 64 位后 Unity 游戏在 Android 设备上滞后