带有嵌套 JSON 的 React/Redux mapStateToProps

IT技术 javascript reactjs redux
2021-05-18 23:08:34

我有一个解析 JSON 的 redux 组件(在底部),但我不知道如何获取嵌套的子对象。我认为我没有正确理解 mapStateToProps 的工作原理。

控制台日志正在转储子对象,但是当我尝试访问 services.name 时,我得到

"Cannot read property 'name' of undefined"

有人可以帮助我了解如何在此处映射属性吗?我在底部包含了一个我从 API 中获取的 JSON 示例。

服务-list.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';

class ServicesList extends Component {

  componentDidMount(){
    this.props.fetchServices();
  }

  render() {
    //console.log('render called in ServicesList component');
    return (
      <table className='table table-hover'>
        <thead>
          <tr>
            <th>Service Name</th>
          </tr>
        </thead>
        <tbody>
          {this.props.services.map(this.renderServices)}
        </tbody>
      </table>
    );
  }

  renderServices(data) {
    console.log(data.services);
    const name = data.services.name;
    return(
      <tr key={name}>
        <td>{name}</td>
      </tr>
    );
  }
}

function mapStateToProps({services}) {
  return { services };
}

export default connect(mapStateToProps, actions)(ServicesList);

我的 JSON 如下所示:

{
  "services": [
     {
        "name": "redemption-service",
        "versions": {
            "desired": "20170922_145033",
            "deployed": "20170922_145033"
        },
        "version": "20170922_145033"
    }, {
        "name": "client-service",
        "versions": {
            "desired": "20170608_094954",
            "deployed": "20170608_094954"
        },
        "version": "20170608_094954"
    }, {
        "name": "content-rules-service",
        "versions": {
            "desired": "20170922_130454",
            "deployed": "20170922_130454"
        },
        "version": "20170922_130454"
    }
  ]
}

最后,我有一个在这里公开 axios.get 的操作:

import axios from 'axios';

const ROOT_URL=`http://localhost:8080/services.json`;

export const FETCH_SERVICES = 'FETCH_SERVICES';

export function fetchServices(){
  const url = `${ROOT_URL}`;
  const request = axios.get(url);

  return{
    type: FETCH_SERVICES,
    payload: request
  };
}
1个回答

我假设你想this.props.fetchServices()将更新services减速,然后将通过services经由propsmapStateToProps
如果这是正确的,请注意您是在内部取物componentWillMount,这是一个很大的禁忌
引用自componentWillMountDOCS

避免在此方法中引入任何副作用或订阅。

您应该在componentDidMount.

此外,您可能认为在您从 ajax 请求中取回数据之前不会调用 render 方法。你看,react 不会等待你的 ajax 调用取回数据,render无论如何都会调用方法,所以第一次render调用将尝试map在一个空数组上services(你有一个空数组作为初始状态我假设你的减速器)。
那么你的renderServices功能将得到一个空数组datadata.services确实undefined因此,当您试图访问data.services.name你的错误:

“无法读取未定义的属性‘名称’”

只需在渲染中使用条件:

<tbody>
  {this.props.services && this.props.services.map(this.renderServices)}
</tbody>

编辑
作为您评论的后续行动,您正在尝试映射object.map适用于数组。所以实际上你应该映射services.services.map(...)而不是services.map,虽然你仍然需要检查它是否存在。
我用你的代码做了一个工作示例,我没有包含 redux 和 ajax 请求,但我使用了你正在使用的相同数据,我只在第二次渲染时传递它ServicesList,所以它基本上与你的场景相同面对。
我什至添加了一个超时来模拟延迟 + 添加一个加载指示器来演示您可以(或应该)使用条件渲染做什么。

const fakeData = {
  services: [
    {
      name: "redemption-service",
      versions: {
        desired: "20170922_145033",
        deployed: "20170922_145033"
      },
      version: "20170922_145033"
    },
    {
      name: "client-service",
      versions: {
        desired: "20170608_094954",
        deployed: "20170608_094954"
      },
      version: "20170608_094954"
    },
    {
      name: "content-rules-service",
      versions: {
        desired: "20170922_130454",
        deployed: "20170922_130454"
      },
      version: "20170922_130454"
    }
  ]
};

class ServicesList extends React.Component {
  componentDidMount() {
    this.props.fetchServices();
  }

  render() {
    const { services } = this.props;

    return (
      <table className="table table-hover">
        <thead>
          <tr>
            <th>Service Name</th>
          </tr>
        </thead>
        <tbody>
          {services.services ? (
            services.services.map(this.renderServices)
          ) : (
            this.renderLoader()
          )}
        </tbody>
      </table>
    );
  }

  renderLoader() {
    return (
      <tr>
        <td>Loading...</td>
      </tr>
    );
  }

  renderServices(data) {
    const name = data.name;
    return (
      <tr key={name}>
        <td>{name}</td>
      </tr>
    );
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: {}
    };
    this.fetchServices = this.fetchServices.bind(this);
  }

  fetchServices() {
    setTimeout(() => {
      this.setState({ data: { ...fakeData } });
    }, 1500);
  }

  render() {
    const { data } = this.state;
    return (
      <div>
        <ServicesList services={data} fetchServices={this.fetchServices} />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>