简体   繁体   English

ReactJs在状态改变时重新渲染

[英]ReactJs re-render on state change

I am trying to do a sequence of fetching some API data and manipulating it as follows: 我正在尝试执行以下操作以获取一些API数据并按如下方式进行操作:

  1. Fetch list of string addresses via API 通过API获取字符串地址列表
  2. Convert each of this string addresses to a geocoded location via API 通过API将每个字符串地址转换为地理编码的位置
  3. Displaying these geocoded addresses as markers on a map 在地图上将这些经过地理编码的地址显示为标记

I am having some trouble with getting the timing with all these asynchronous activities right (I am pretty new to Javascript). 我在正确安排所有这些异步活动的时间方面遇到了一些麻烦(对于Javascript我是很陌生的)。

Here's what I have so far: 这是我到目前为止的内容:

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....
    .....
    }
}

So I don't quite understand why React does not re render when I do this.setState({ geocodedAddresses }) - Shouldn't react re render when I do setState? 所以我不太明白为什么我这样做时React不会重新渲染this.setState({ geocodedAddresses }) -当我执行setState时不应该重新渲染吗?

There are a couple errors with your code. 您的代码有几个错误。 In the first place, the state object is being created with equals instead of colons: 首先,使用equals而不是冒号创建状态对象:

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

In the second place, you should take into account that your code is asynchronous. 其次,您应考虑到代码是异步的。 Only when the promises generated by the call to Geocode.fromAddress resolve you have the data for your geocodedAddresses . 只有当通过调用产生的诺言Geocode.fromAddress解决您为您的数据geocodedAddresses

In the componentDidMount , you are console logging the geocodedAdresses and you report that you are seeing the right values. componentDidMount ,您正在控制台记录geocodedAdresses并报告看到了正确的值。 This is only because the log is update after the promises resolve. 这仅是因为在承诺解决之后,日志已更新。 But when you do the console.log the value at that moment for geocodedAdresses is an empty array. 但是,当你做console.log在那一刻的价值geocodedAdresses是一个空数组。 And that is the value that is being inserted in the component state . 这就是在组件state插入的值。

In order to set the correct value for the state, you should call setState when all your Geocode.fromAddress promises have resolved. 为了为状态设置正确的值,您应该在所有Geocode.fromAddress承诺均已解决后调用setState In order to do that you can use Promise.all method. 为此,您可以使用Promise.all方法。

So, your code would look like: 因此,您的代码如下所示:

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....
    .....
    }
}

Note that with this solution setState is only being called once. 请注意,使用此解决方案, setState仅被调用一次。

Since all your state refers to addresses information, it could make sense to merge that information into a single key in the state . 由于您所有的state涉及到地址信息,因此有可能将该信息合并到state的单个键中。 You could initialize your state in the constructor as: 您可以在构造函数中将state初始化为:

this.state = {
    addresses: []
};

And then, you could populate the state once all the promises resolve: 然后,一旦所有承诺都解决,您就可以填充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 })
                });
        }))
    }
}

You're right it's the async is slightly off. 您说对了,这是异步稍微关闭了。 The console.log will populate after it "appears" in the console, but when setState has been called (and also when console.log has been called) its value is still [] . console.log它“出现”在控制台中填充,但是当setState被调用时(以及console.log被调用时),它的值仍然是[]

if you're using async/await you can wait for the fetch chain to completely finish, or put the setState within the then . 如果您使用的是async/await ,则可以等待fetch链完全完成,或者将setState放在then With the setState callback, it's probably better to do the latter. 使用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 })
}))

}}

or 要么

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 })

    }}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM