如何映射 API 与多个对象 [Spree API V2 & ReactJS]

How to map API with multiple objects [Spree API V2 & ReactJS]

我正在构建一个网上商店,前端使用 ReactJS,后端使用 Spree (Ruby)。

Spree 提供了一种 API 解决方案来连接前端和后端。

我正在尝试显示带有产品图片的产品,但 Spree 的 API 是以特定方式设置的,即产品图片和产品不在同一对象中。

API 响应是:

 {
    (holds products)data: [],
    (Holds product images)included:[],
 }

我的目标是创建一个显示产品信息和产品图片的 ul

我已经尝试将 my API link 映射到

           this.state.arrays.map((product) => 
              product.data
            )

它用数据对象响应,但我不能例如做 product.data.name 因为它 returns 一个 undefined 响应

日志中的数据响应

ProductsList.js:28 PL 
[undefined]
Index.js:42 productsData 
{}
ProductsList.js:28 PL 
[Array(5)]
0: Array(5)
0: {id: "5", type: "image", attributes: {…}}
1: {id: "4", type: "image", attributes: {…}}
2: {id: "1", type: "image", attributes: {…}}
3: {id: "3", type: "image", attributes: {…}}
4: {id: "2", type: "image", attributes: {…}}
length: 5
__proto__: Array(0)
length: 1
__proto__: Array(0)

产品索引页

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from "prop-types";
import ProductsList from "./products/ProductsList";
import axios from 'axios';



const REACT_VERSION = React.version;
const include = '?include=images';
const API = 'https://stern-telecom-react-salman15.c9users.io/api/v2/storefront/products' + include;

const styles = {
  card: {
    maxWidth: 345,
  },
  media: {
    height: 140,
  },
};

class Index extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            products: [],
            productsData: {},
            isLoading: false,
            error: null,
    };
  }
  componentDidMount() {
    this.setState({ isLoading: true });
    axios.get(API)
      .then(result => this.setState({
        products: result.data.data,
        productsData: result.data,
        isLoading: false,
      }))
      .catch(error => this.setState({
        error,
        isLoading: false
      }));
      // console.log(
      //   'productsData', 
      //   this.state.productsData

      //   )
  }
  render() {
    const { products, productsData,isLoading, error } = this.state;

    if (error) {
      return <p>{error.message}</p>;
    }
     if (isLoading) {
      return <p>Loading ...</p>;
    }
    return (
      <React.Fragment>
          <h1>React version: {REACT_VERSION}</h1>
          <ProductsList products={this.state.productsData}/>
      </React.Fragment>
    );
  }
}

ProductsList.propTypes = {
  greeting: PropTypes.string
};

export default Index

产品列表页面

import React from "react"
import PropTypes from "prop-types"

import { withStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardActionArea from '@material-ui/core/CardActionArea';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';

const url = "https://stern-telecom-react-salman15.c9users.io"

class ProductsList extends React.Component {
  constructor(props) {
    super(props);
    const { products } = this.props;
    const arrays = Object.values( {products} );
    this.state = {
      products,
      arrays
    };
  }
  render () {
    return (
      <React.Fragment>
        <ul>
          <p>Shop Products</p>
          {
          // console.log(
          //   'PL',
            // this.state.arrays.map((product) => 
            //   product.data
            // )
          // )
          this.state.arrays.map(product =>
            <li key={product.objectID}>
            <Card>
                  <CardActionArea>
                    <CardMedia
                      image= {url + ''}
                      title={product.data.attributes.name}
                    />
                    <CardContent>
                      <Typography gutterBottom variant="h5" component="h2">
                       {product.data.attributes.name}
                      </Typography>
                      <Typography component="p">
                        {product.data.attributes.description}
                      </Typography>
                    </CardContent>
                  </CardActionArea>
                  <CardActions>
                    <Button size="small" color="primary">
                     {product.data.attributes.display_price} 
                    </Button>
                    <Button size="small" color="primary">
                      add to cart
                    </Button>
                  </CardActions>
                </Card>
            </li>
            )
          }
        </ul>
      </React.Fragment>
    );
  }
}

ProductsList.propTypes = {
  greeting: PropTypes.string
};
export default ProductsList

我希望得到的结果是产品信息和图片

您获取 json 数据后的操作是错误的。返回的结果是一个 json 对象,带有 data 属性 是你想要传递并获取产品的数组。

您要么将 products 传递给 <ProductsList> 组件:

const { products, images, isLoading, error } = this.state;
...
<ProductsList products={products} images={images}/>

然后直接使用:

class ProductsList extends React.Component {
  constructor(props) {
    super(props);
    const { products, images } = this.props;
    this.state = {
      products,
      images
    };
    ...
  }
  ...
}

或者使用props.products.data直接获取ProductsList构造函数中的products数组:

class ProductsList extends React.Component {
  constructor(props) {
    super(props);
    const products = this.props.products.data;
    const images = this.props.products.included;
    ...
  }
  ...
}

不需要使用 const arrays = Object.values({ products });,因为您已经有了包含产品的数组:

...
products: result.data.data,   // products is an array with products
images: result.data.included, // images is an array with all posible images
productsData: result.data,    // productsData.data is an array with products
...

此外,产品对象不包含任何名为 data:

的属性
<Typography gutterBottom variant="h5" component="h2">
  {product.data.attributes.name}
</Typography>
<Typography component="p">
  {product.data.attributes.description}
</Typography>

您必须像这样直接访问它的属性:

<Typography gutterBottom variant="h5" component="h2">
  {product.attributes.name}
</Typography>
<Typography component="p">
  {product.attributes.description}
</Typography>

编辑

这是一个 CodeSandbox project with your code simpified and without calling the Axios request (because it's restrticted) 并将数据放在 JSON 文件中。您还应该将 isLoading 初始化为 true,或者使 Index 组件在有一些数据之前不呈现:

class Index extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      ...
      isLoading: true,
    }
  }
}

这是一个更新后的屏幕截图:

以及简化的 <ProductsList/> 组件:

import React from "react";

const url = "https://stern-telecom-react-salman15.c9users.io";

class ProductsList extends React.Component {
  constructor(props) {
    super(props);

    const { products, images } = this.props;
    //const arrays = Object.values( {products} );
    this.state = {
      products,
      images
      //arrays
    };
  }
  render() {
    const { products, images } = this.state;
    return (
      <React.Fragment>
        <p>Shop Products</p>
        {console.log("PL", products, images)
        // this.state.arrays.map(product =>
        //   <li key={product.objectID}>

        //   </li>
        //   )
        }
        <ul>
          {products.map(product => (
            <li key={product.key}>
              <h4>{product.attributes.name}</h4>
              <p>Description: {product.attributes.description}</p>
              <p>Price: {product.attributes.display_price} </p>
              <p>Images:</p>
              <div>
                {product.relationships.images.data.map(({ id }) => {
                  let image = images.find(image => image.id == id);
                  return image ? (
                    <img src={`${url}/${image.attributes.styles[1].url}`}/>
                  ) : null;
                })}
              </div>
            </li>
          ))}
        </ul>
      </React.Fragment>
    );
  }
}

export default ProductsList;

编辑 2

添加图像是一项非常简单的任务。您只需将产品数组与图像结合起来并显示图像。检查更新的 <ProductsList/> 组件。当然,您必须将 productsimages 都传递给 <ProductsList/> (const images = productsData.included;)。检查更新后的 CodeSandbox<ProductsList/> 组件和屏幕截图。

编辑 3

关于图片;每个图像都有一个 styles 属性 这是一个不同大小的数组:

"included": [
{
  "id": "5",
  "type": "image",
  "attributes": {
    "viewable_type": "Spree::Variant",
    "viewable_id": 4,
    "styles": [
      {
        "url": "...",
        "width": "48",
        "height": "48"
      },
      {
        "url": "...",
        "width": "100",
        "height": "100"
      },
      {
        "url": "...",
        "width": "240",
        "height": "240"
      },
      {
        "url": "...",
        "width": "600",
        "height": "600"
      }
    ]
  }
}
...
]

为了将图像映射到每个产品,我们必须使用 product.relationships.images.data 映射存储在每个产品中的所有图像,product.relationships.images.data 是一个包含 id 和 [=41= 的对象数组] 特性。对于产品图片中的每张图片,我们使用 let image = images.find(image => image.id == id) 搜索图片数组,如果找到图片,则使用四种可用尺寸之一或可能使用所有可用尺寸(48px100px, 240px, 600px);我选择 image.attributes.styles[1].url,所以我显示可用图像尺寸的第二个元素,即 100px 尺寸图像:

product.relationships.images.data.map(({ id }) => {
  let image = images.find(image => image.id == id);
  return image ? (
    <img src={`${url}/${image.attributes.styles[1].url}`}/>
  ) : null;
})

编辑 4

如果您需要为每个产品获取一张图片,那么您可以使用一个函数来检查图片是否存在,然后从图片数组中获取图片:

// Here we're using an inline function to get the product image
// You can also create a normal class function and use that instead

{product.relationships.images.data.length > 0 &&
  (() => {
    // { id } is the destructure of product.relationships.images.data[0]
    // which means it extract the property id to a stand alone variable
    const { id } = product.relationships.images.data[0];
    const image = images.find(image => image.id == id);
    return image ? (
      <img src={`${url}/${image.attributes.styles[1].url}`} />
    ) : null;
  })()
}

这是一个隔离其内容并立即执行的内联函数:

(() => { ... })()

您可以阅读有关 解构赋值的更多信息 ({ id } = object).