将对象数组映射到 React Elements 时,其中一个属性消失了

While mapping an array of objects to React Elements, one of the properties disappears

我正在尝试制作一个 React 应用程序以从宠物小精灵 api (https://pokeapi.co/docs/v2#pokemon) 获取数据并将其显示在网格中。

我的组件树:

应用程序 -> Pokedex -> n(PokeCard -> PokeAvatar)

在 App 组件中,我获取了 pokemon 数据,我得到了 20 个第一个结果并将结果数组映射到一个数组,其中每个 Pokemon 的 URL 也被获取(数组中的每个对象都有'name'、'url' 和 'info' 属性)。信息 属性 包含每个单独提取的所有数据。

在使用此数组作为道具渲染 Pokedex 之后,在 Pokedex 组件中,我将数组映射到仅包含我要显示的数据(名称和 'info' 中的一些属性)的元素数组属性).

这是引发错误的代码:

export class Pokedex extends React.Component {
  render() {
    console.log("this.props.pokeArray:", this.props.pokeArray); // shows pokemons with the 3 properties
    const elements = this.props.pokeArray.map((pokemon, i) => {
      console.log(pokemon); // logs the pokemon without the info property
      return (
        <PokeCard
          key={`poke${i}`}
          id={pokemon.info.id} //error raised here: Cannot read property 'id' of undefined
          name={pokemon.name}
          types={pokemon.info.types}
          sprite={pokemon.info.sprites["front_default"]}
        />
      );
    });

    return (
      <div className="pkdx-pokedex-container">
        <h1>Pokedex</h1>
        {elements}
      </div>
    );
  }
}

这也是来自其父元素 App 的代码:

import "./App.css";
import { Pokedex } from "./components/Pokedex/Pokedex";
import { useQuery } from "react-query";
import { ReactQueryDevtools } from "react-query-devtools";

// *** Base API url

const url = "https://pokeapi.co/api/v2/pokemon";

// *** Async, because we need to have the data before second fetch

async function fetchPokemon() {
  const response = await fetch(url);

  const data = (await response.json()).results;

  // *** Keep url of fetch and add new info property to each pokemon

  data.forEach(async (poke) => {
    const res = await fetch(poke.url);
    poke.info = await res.json();
  });

  return data;
}

function App() {
  const info = useQuery("fetchPokemon", fetchPokemon);

  if (info.status === "success") {
    console.log("pokeArray:", info.data); // each Pokemon has the three properties
    return (
      <div>
        <Pokedex pokeArray={info.data} />;
        <ReactQueryDevtools />
      </div>
    );
  } else return null;
}

export default App;

我不知道我是否遗漏了什么,但我不明白为什么它不显示 'info' 属性.

看起来您正在执行异步 forEach 而不是等待它。您可能需要更改为 map 并执行 const data = await Promise.all(data.map(...)) 以确保您的数据已加载。

我整理了一个working example。看看:

import React from "react";
import { useQuery } from "react-query";
import { ReactQueryDevtools } from "react-query-devtools";

export class Pokedex extends React.Component {
  render() {
    console.log("this.props.pokeArray:", this.props.pokeArray); // shows pokemons with the 3 properties
    const elements = this.props.pokeArray.map((pokemon, i) => {
      console.log("POKEMON", pokemon); // logs the pokemon without the info property
      return (
        <React.Fragment key={i}>
          <div>key={`poke${i}`}</div>
          <div>id={pokemon.info.id}</div>
          <div>name={pokemon.name}</div>
          <div>sprite={pokemon.info.sprites["front_default"]}</div>
        </React.Fragment>
      );
    });

    return (
      <div className="pkdx-pokedex-container">
        <h1>Pokedex</h1>
        {elements}
      </div>
    );
  }
}

// *** Base API url

const url = "https://pokeapi.co/api/v2/pokemon";

// *** Async, because we need to have the data before second fetch

async function fetchPokemon() {
  const response = await fetch(url);

  const data = (await response.json()).results;

  // *** Keep url of fetch and add new info property to each pokemon

  const x = await Promise.all(
    data.map(async (poke) => {
      const res = await fetch(poke.url);
      return {
        ...poke,
        info: await res.json()
      };
    })
  );

  return x;
}

function App() {
  const info = useQuery("fetchPokemon", fetchPokemon);

  if (info.status === "success") {
    console.log("pokeArray:", info.data); // each Pokemon has the three properties
    return (
      <div>
        <Pokedex pokeArray={info.data} />;
        <ReactQueryDevtools />
      </div>
    );
  } else return null;
}

export default App;

这里的问题在于fetchPokemon。在 forEach 回调中使用 await fetch(poke.url) 时,回调将愉快地等待响应。但是 forEach 不处理回调所承诺的 return。这意味着 pokemon.info 属性是在 datafetchPokemon 函数 return 编辑后的某个时间设置的。

要解决此问题,请使用 map() to store the resulting promises, then Promise.all() 等待要解决的承诺列表。

async function fetchPokemon() {
  const response = await fetch(url);
  const data = (await response.json()).results;

  await Promise.all(data.map(async (pokemon) => {
    const res = await fetch(pokemon.url);
    pokemon.info = await res.json();
  }));

  return data;
}

异步函数始终return是一个承诺,因此将异步函数传递给map()会将当前数组元素映射到承诺。然后可以将该数组传递给 Promise.all() ,它将等待所有承诺完成。承诺将全部解析为 undefined,因为 map() 回调中没有 return 值,但是数据存储在 data 中,因此我们可以 return相反。

const url = "https://pokeapi.co/api/v2/pokemon";

async function fetchPokemon() {
  const response = await fetch(url);
  const data = (await response.json()).results;

  await Promise.all(data.map(async (pokemon) => {
    const res = await fetch(pokemon.url);
    pokemon.info = await res.json();
  }));

  return data;
}


fetchPokemon().then(pokemon => {
  document.body.textContent = JSON.stringify(pokemon);
});