首页 > 解决方案 > 我的 React 组件在 props 数据可用之前渲染,使用钩子

问题描述

所以我正在创建一个简单的瓷砖匹配应用程序。我有一个 tile 组件,该组件从包含该 tile 的游戏板组件中获取道具,并且该组件本身位于应用程序中,显然具有主要逻辑和状态数据。

我需要每个 Tile 都有自己的逻辑来知道它是否应该翻转。

我遇到的问题是,当我创建一个状态数据时,瓷砖被翻转和/或应该保持翻转,瓷砖正在使用以前的渲染数据运行它们的组件级逻辑。

这意味着我匹配两个图块,它们被添加到相关状态 - selectedTiles 和matchedTiles 被传递给所有图块。瓦片检查 selectedTiles 中是否有两个值,如果有,则检查 matchTiles 以查看该值是否与组件级别的 prop 匹配。但是,由 selectedTiles 更改触发的检查matchedTiles 的逻辑在matchedTiles 道具到达组件之前运行。

我知道这是因为状态快照,但我不确定如何实际解决问题。

对任何误解或解释表示歉意,因为这是我第一个完全自行编写的 react 应用程序。

我在下面的 git 上链接了存储库。

https://github.com/Samuel-Programmer/React-Tiles

标签: reactjs

解决方案


我拉了你的回购并稍微更改了代码。

在我看来,最好使用 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%;
  }
`;

推荐阅读