反应保持旧状态 - 新状态未更新

时间:2021-02-25 15:00:31

标签: reactjs state

在 React 项目中,我有一个包含一系列游戏的状态 gameResults,我有一个函数可以根据查询获取游戏列表:

useEffect(() => {
    const timeoutId = setTimeout(() => {
      if (gameQuery.length > 0) {
        axios.get(`/api/games/${gameQuery}`).then((response) => {
          const igdbGames: IGDBGame[] = response.data.games;
          const formatedGames = formatGames(igdbGames);
          setGameResults(formatedGames);
        });
      }
    }, 300);
    return () => clearTimeout(timeoutId);
  }, [gameQuery]);

对于每场比赛,我没有封面,所以我得到了每场比赛的封面:

const loadGamesImages = async () => {
    for (let i = 0; i < gameResults.length; i++) {
      axios
        .get(`/api/cover/${gameResults[i].id}`)
        .then((response) => {
          const coverUrl: IGDBCover = response.data.covers[0];
          const newGame = {
            ...gameResults[i],
            cover: coverUrl.url.replace("//", "https://"),
          };
          const newGames = gameResults.filter(
            (game: Game) => game.id !== newGame.id
          );
          setGameResults([...newGames, newGame]);
        })
        .catch((error) => {
          console.log("error", error);
        });
      await sleep(300);
    }
    console.log("finish");
  };

  useEffect(() => {
    loadGamesImages();
  }, [gameResults.length]);

这是我的问题:当 React 更新状态时,旧状态不再存在。我解释说:对于第一个封面,新状态已经涵盖了第一场比赛。但是当他为第二场比赛创建一个新状态时,正如你所看到的,我得到了 gameResults 状态,但在这一场比赛中,第一场比赛不再有掩护。

结果如下: enter image description here

我做错了什么?

1 个答案:

答案 0 :(得分:1)

您的每个循环异步调用都会在有状态 gameResults 的初始绑定上关闭 - 而 gameResults 开始时为空。例如,对于第一个解析的 Promise,这些行:

const newGames = gameResults.filter(
    (game: Game) => game.id !== newGame.id
);
setGameResults([...newGames, newGame]);

gameResults 指代空数组,所以 setGameResults 正确分布空数组加上刚刚添加的 newGame

但是在进一步的 Promise 解决方案中,它们关闭了最初为空的 gameResults - 所有异步调用都发生在组件重新渲染之前。

改用回调,这样异步调用就不会相互覆盖:

setGameResults((gameResults) => {
    const newGames = gameResults.filter(
        (game) => game.id !== newGame.id
    );
    return [...newGames, newGame];
});

(还要注意,没有必要明确指出 TS 已经可以自动推断出的参数类型:(game: Game) 可以只是 game

一旦它起作用了,我还建议调整您的代码,以便当效果钩子再次运行时,只有尚未被检索的封面会再次被请求。这将使您免于不必要地重复请求。