ReactJs在状态改变时重新渲染

时间:2019-09-08 16:49:03

标签: javascript reactjs asynchronous

我正在尝试按以下步骤提取一些API数据并对其进行处理:

  1. 通过API获取字符串地址列表
  2. 通过API将每个字符串地址转换为经过地理编码的位置
  3. 将这些经过地理编码的地址显示为地图上的标记

我在正确安排所有这些异步活动的时间方面遇到了一些麻烦(我对Java语言还很陌生)。

这是我到目前为止所拥有的:

class Map extends Component {
    constructor(props) {
        super(props);

        this.state = {
            addresses = [],
            geocodedAddresses = []
        }
    }

    componentDidMount() {
        const geocodedAddresses = []
        fetch('.....')
            .then(result => result.json())
            .then(addresses => this.setState({ addresses }, function() {
                this.state.addresses.forEach(address => {
                    Geocode.fromAddress(address).then(geocodedAddress => {
                        geocodedAddresses.push(geocodedAddress['results'][0]['geometry']['location'])
                    })
                })
            }))
            console.log(geocodedAddresses) //Correctly contains the geocoded addresses
            this.setState({ geocodedAddresses })
        }
    }

    render() {
        this.state.addresses.map(a => console.log(a)) //Evaluates correctly
        this.state.geocodedAddresses.map(ga => console.log(ga)) //Yields nothing....
    .....
    }
}

所以我不太明白为什么this.setState({ geocodedAddresses })时React无法重新渲染-setState时不应该重新渲染吗?

2 个答案:

答案 0 :(得分:1)

您的代码有几个错误。首先,使用等于而不是冒号创建状态对象:

this.state = {
    addresses: [],
    geocodedAddresses: []
}

第二,应该考虑代码是异步的。只有调用Geocode.fromAddress所产生的承诺能够解决您的geocodedAddresses的数据。

componentDidMount中,您正在控制台记录geocodedAdresses,并报告看到了正确的值。这仅是因为在承诺解决之后,日志已更新。但是,当您执行console.log时,geocodedAdresses那时的值是一个空数组。这就是要插入组件state中的值。

为了为状态设置正确的值,当您所有的setState承诺都得到解决后,您应该致电Geocode.fromAddress。为此,您可以使用Promise.all方法。

因此,您的代码如下:

class Map extends Component {
    constructor(props) {
        super(props);

        this.state = {
            addresses: [],
            geocodedAddresses: []
        }
    }

    componentDidMount() {
        fetch('.....')
            .then(result => result.json())
            .then(addresses => {
                Promise.all(addresses.map(address => Geocode.fromAddress(address)))
                    .then(geocodedAddresses => {
                        this.setState({
                            addresses,
                            geocodedAddresses
                        })
                    });
            }))
        }
    }

    render() {
        this.state.addresses.map(a => console.log(a)) //Evaluates correctly
        this.state.geocodedAddresses.map(ga => console.log(ga)) //Yields nothing....
    .....
    }
}

请注意,使用此解决方案,setState仅被调用一次。

由于您所有的state都指向地址信息,因此可以将这些信息合并到state中的单个键中。您可以在构造函数中将state初始化为:

this.state = {
    addresses: []
};

然后,一旦所有承诺都解决,您就可以填充state

componentDidMount() {
    fetch('.....')
        .then(result => result.json())
        .then(addresses => {
            Promise.all(addresses.map(address => Geocode.fromAddress(address)))
                .then(geocodedAddresses => {
                    const addressesState = addresses.map((address, i) => {
                        return {
                            address,
                            geocodedAddress: geocodedAddresses[i]
                        };
                    });
                    this.setState({ addresses: addressesState })
                });
        }))
    }
}

答案 1 :(得分:0)

您说对了,这是异步稍微关闭了。 console.log会在之后填充,它会“出现”在控制台中,但是当调用setState时(以及调用console.log时),其值为仍然[]

如果您使用的是async/await,则可以等待fetch链完全完成,或将setState放在then中。使用setState回调,最好做后者。

componentDidMount() {
  const geocodedAddresses = []
  fetch('.....')
  .then(result => result.json())
    .then(addresses => this.setState({ addresses }, function() {
      this.state.addresses.forEach(address => {
        Geocode.fromAddress(address).then(geocodedAddress => {
          geocodedAddresses.push(geocodedAddress['results'][0]['geometry']['location'])
        })
      })
   console.log(geocodedAddresses) //Correctly contains the geocoded addresses
   this.setState({ geocodedAddresses })
}))

}}

componentDidMount = async () => {
      const geocodedAddresses = []
      let addresses = await fetch('.....').then(result => result.json())
      addresses.forEach(address => { // assuming this is sync
            Geocode.fromAddress(address).then(geocodedAddress => {
              geocodedAddresses.push(geocodedAddress['results'][0]['geometry']['location'])
            })
          })
       this.setState({ geocodedAddresses,addresses })

    }}