与生命周期方法混淆 - 为什么我的 API 调用没有返回数据?

Confusion with lifecycle methods - why is my API call not returning data?

我正在使用 React 构建一个酒吧网站,这需要我获取有关桶装啤酒的信息。我正在从 http://www.hashigozake.co.nz/taplist.xml 中检索该信息,如您所见,数据的格式为 return XML。然后使用名为 xml-react-parser.

的库将此数据转换为 js 对象

无论如何,我编写了以下代码来进行此 API 调用:

    componentDidMount() {
        const url = 'http://www.hashigozake.co.nz/taplist.xml'
        const proxyURL = 'https://cors-anywhere.herokuapp.com/'
      
        fetch(proxyURL + url)
        .then(res => res.text())
        .then(beerXML => {
          let beerJs = new XMLParser().parseFromString(beerXML)
          this.setState({
            beers: beerJs
          })

        })
      }

这就是问题所在。我将 taplist 数据 ({beerList =this.state.beers}) 作为道具传递给另一个名为 TileList.jsx 的组件。在这个组件中,我 运行 以下控制台日志: console.log(props.beerList.children[0]) 应该 return 一个对象数组,但是却给我一条错误消息,说 Uncaught TypeError: Cannot read property '0' of undefined.

我怀疑正在发生的事情是在 fetch() 方法完成 returning 数据之前调用了 render() 方法,这就是为什么我得到一个 undefined 当我尝试访问我的数据。我很困惑,因为我认为 componenentDidMount 是在 render 方法之后触发的,所以我的数据不应该被渲染吗?

无论如何,这里更详细地看一下我的代码:

父组件:Home.jsx

import React from 'react'
import './styles.css'
import hashi_logo_2 from './Photos/hashi_logo_2.png'
import TileList from './TileList'
import OnNextList from './OnNextList'
import {Link} from 'react-router-dom'
const XMLParser = require('react-xml-parser')

class Home extends React.Component {

    constructor() {
        super()
        this.state = {
            beers:[]
        }
    }

    componentDidMount() {
        const url = 'http://www.hashigozake.co.nz/taplist.xml'
        const proxyURL = 'https://cors-anywhere.herokuapp.com/'
      
        fetch(proxyURL + url)
        .then(res => res.text())
        .then(beerXML => {
          let beerJs = new XMLParser().parseFromString(beerXML)
          this.setState({
            beers: beerJs
          })

        })
      }

    render() {
    return (
        <div className = 'home'>
            <nav className = 'nav'>
                <ul>
                    <Link to = '/about' style={{ textDecoration: 'none' }} >
                        <li className='list'>About</li>
                    </Link>
                    <li className='list'>Food</li>
                    <li className='list'>Events</li>
                    <li className='list'>News</li>
                    <Link to ='/shop' style={{ textDecoration: 'none' }} >
                        <li className='list'>Shop</li>
                    </Link>
                    <li className='list'>Contact</li>
                    <li className='list'>Blog</li>
                </ul>
            </nav>
            <main>
                <div className='main__image'>
                    <img src= {hashi_logo_2}/>
                </div>
                <div>
                    <TileList beerList = {this.state.beers}/>
                    <OnNextList/>
                </div>
            </main>
        </div>
    )
}
}

export default Home

子组件:TileList.jsx

import React from 'react'
import Tile from './Tile'

const TileList = (props) => {
    console.log(props.beerList.children[0])
    return (
        <>
         <h1 className = 'h1-pouring'>Currently Pouring:</h1> 
        <div className = 'tile-container'>
            {
                props.beerList.map(item => {
                    return <Tile 
                    />
                })
            } 
        </div> 
        </>
    )
}

export default TileList

此外,这是作为 props 发送的数据的(不完整)视图:

{
  name: 'Products', attributes:{}, value:'', children: [
    {
      name:'Beers', attributes: {}, value: '', children: [
        {
          name: 'Product', attributes:{}, value:'', children: {
            
          }
        }
      ]
    }
  ]
}

根据react文档here,setState是异步的,在第一次渲染中,props.beerList是一个空数组,然后setState()会触发第二次渲染,所以你可以通过如果您的 return 包含一个值,则第二个渲染中的索引。

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.

问题

您的初始状态没有提供足够的数据结构,无法更深入地正确访问。

beers 对象最初在 Home

中定义为一个空数组
this.state = {
  beers: [],
}

并作为 prop 传递给 TileList

<TileList beerList={this.state.beers} /> // this.state.beers ~ []

TileList 内,您尝试立即更深入地记录

console.log(props.beerList.children[0]);

检查一下,props.beerList 当前是空数组 ([]),没有 children 的 属性,即 props.beerList.childrenprops.beerList['children'] 未定义,因此尝试访问索引 0 将引发错误 Uncaught TypeError: Cannot read property '0' of undefined.

次要问题

初始状态为beers = [],但后来响应形状为对象

{
  name: 'Products', attributes:{}, value:'', children: [
    {
      name:'Beers', attributes: {}, value: '', children: [
        {
          name: 'Product', attributes:{}, value:'', children: {
            
          }
        }
      ]
    }
  ]
}

这种不一致的状态类型会使调试代码变得更加困难,或者换句话说,更容易出现错误代码。

解决方案

保持状态类型一致。

this.state = {
  beers: {},
}

首先使用守卫或可选链接进行检查

props.beerList.children &&
props.beerList.children.length && 
console.log(props.beerList.children[0]);

console.log(props.beerList?.children?.[0]);

访问正确的嵌套 属性 以映射啤酒

props.beerList.children &&
props.beerList.children.length && 
props.beerList.children[0].children && 
props.beerList.children[0].children.map(product => ...