【问题标题】:How do I update a React state from previously fetched data?如何从之前获取的数据中更新 React 状态?
【发布时间】:2020-10-05 03:00:26
【问题描述】:

我是 React 和 JS 的新手,仍在学习,我正在尝试创建一个网站来显示来自 SWAPI API 的数据,特别是人员数据。如果我获取第一页的人,有一个名为“next”的字段,其中包含检索下一页的 url,依此类推。我希望能够在循环中使用该字段来获取所有数据页面。这是第一页的示例:

{
    "count": 82,
    "next": "http://swapi.dev/api/people/?page=2",
    "previous": null,
    "results": [
        {
            "name": "Luke Skywalker",
            "height": "172",
            "mass": "77",
            "hair_color": "blond",
            "skin_color": "fair",
            "eye_color": "blue", ...

所以,一旦我获取了/people/,我就会想获取/people/?page=2 这是我到目前为止的相关代码......

class App extends Component {
    constructor() {
        super()
        this.state = {
            people: [],
            url: '',
            searchfield: ''
        }
    }
    // Find first page of 'people' then loop through each page adding 'data.results' to 'people' state array
    componentDidMount() {
        this.setState({ url: 'https://swapi.dev/api/people/' }, () => {
            console.log('initial url is', this.state.url)   
                for (var i = 0; i <= 3; i++) {
                    console.log('next url is', this.state.url)
                    fetch(this.state.url)
                    .then(response => response.json())
                    .then(data => this.setState((prevstate) => {
                        return {people: prevstate.people.concat(data.results), url: data.next}
                        }))
                    .catch(error => {
                        console.log(error)
                    });
                }
            });
        }

    componentDidUpdate(prevProps,prevState) {
        if(prevState.url !== this.state.url) {
            console.log('* new url is', this.state.url)
        }
    }

我现在有一个固定循环i,直到我可以让它正常工作,否则它会无限循环。 我的问题是,当尝试使用下一页的地址更新 url 状态时,直到循环完成才会发生,这是上面日志的输出。

* new url is https://swapi.dev/api/people/
initial url is https://swapi.dev/api/people/
4 next url is https://swapi.dev/api/people/  < this happens 4 times
* new url is http://swapi.dev/api/people/?page=2

我认为在返回字段中添加状态更改就足够了,但事实并非如此,所以我尝试添加 componentDidUpdate 函数来尝试触发状态更改,但我认为它没有帮助。我也很好奇 componentDidUpdate 的日志在没有更新的情况下是如何首先出现的?

目前所做的只是将相同的 4 个页面连接到 people 数组中(控制台抱怨它)。

所以我的问题是,如何使用之前获取的数据正确设置 url 状态?

编辑:好的,我忘记添加到我的问题的一件事是,我计划使这个 fetch 通用,以便它可以接受 SWAPI 上的任何类别并使用“下一个”字段来确定何时停止提取数据。我确实有一段类似于 Yousafs 答案的前一段代码,我获取了第一页,然后使用计数循环遍历所有单独的页面,但这意味着 72 次获取!那时循环浏览页面似乎是一个更好的选择。我现在有更好的想法了。

【问题讨论】:

    标签: javascript reactjs fetch state


    【解决方案1】:

    fetch()asynchronous。当您调用fetch 时,它会立即返回Promise 并安排稍后执行实际提取。它实际上并没有像您在其他语言/框架中习惯的那样内联工作。

    因此,您的 for 循环从 0 开始,调用 fetch 安排使用当前 URL 进行提取(但实际上不执行提取),转到 1,安排使用相同的 URL(请记住,它没有改变,因为之前的 fetch 还没有真正执行),等等。

    一旦您的for 循环和回调函数退出,fetchs 就会执行。现在,在 React 中,每次更新状态时,组件都会重新渲染。这计算起来非常昂贵,因此 React 尝试尽可能少地执行此操作。 React 进行的优化之一是它makes state changes asynchronous 以便将一行中的一堆状态更改聚合成一个大的状态更改。您只会看到来自 componentDidUpdate 的一个日志,因为它实际上只更新了一次。

    有几种方法可以解决这个问题。我的建议是使用 JavaScript 的 async/await 语法。将async 放在原始setState 回调中的箭头函数之前,并将await 放在fetch 之前。

    【讨论】:

      【解决方案2】:

      您在循环中获取数据的方法都是错误的。 fetch 函数返回一个承诺,实际请求是异步发出的。您应该做的是将fetch 函数返回的所有承诺保存在一个数组中,然后使用Promise.all() 函数解决所有这些承诺。

      要获取您想要的数据,请按以下步骤操作:

      1. componentDidMount 函数中,创建一个数组来保存fetch 函数返回的所有承诺。创建一个循环并将fetch 函数返回的所有promise 添加到您之前创建的函数中。

      2. 之后,调用Promise.all 函数并传递包含所有承诺的数组。 Promise.all 将返回一个 Response 对象数组。然后您需要对所有这些Response 对象调用.json() 函数以获取API 返回的实际数据。

      这是您的componentDidMount 函数的外观。

      componentDidMount() {
          const requests = [];
      
          for (let i = 1; i <= 3; i++) {
            requests.push(fetch('https://swapi.dev/api/people/?page=' + i));
          }
      
          Promise.all(requests)
            .then(res => Promise.all(res.map(r => r.json())))
            .then(data => {
              const people = [];
      
              data.forEach(d => people.push(...d.results));
      
              this.setState({ people });
            })
            .catch(err => console.log(err.message));
      }
      

      上面写的componentDidMount函数也可以写成async-await语法。

      async componentDidMount() {
          const requests = [];
      
          for (let i = 1; i <= 3; i++) {
            requests.push(fetch('https://swapi.dev/api/people/?page=' + i));
          }
      
          try {
            const responseArr = await Promise.all(requests);
            const data = await Promise.all(responseArr.map(r => r.json()));
      
            const people = [];
            data.forEach(d => people.push(...d.results));
      
            this.setState({ people });
      
          } catch(error) {
            console.log(error.message);
          }
      }
      

      演示

      这是一个demo,它从 Swapi API 获取前 3 页的数据并显示所有人的姓名。

      【讨论】:

      • 我已将您的回复标记为答案,因为它可以帮助我更好地理解异步请求,但如果您阅读我的问题编辑,我忘了添加我打算做什么。我现在有一个想法,我可以使用您的方法,但使用“计数”字段来确定在处理它们之前我需要将多少页捆绑到承诺数组中(我不知道你可以这样做!),即count/10 如果余数不为零则加 1,否则按原样使用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-25
      • 2021-01-09
      • 2021-11-23
      • 2021-11-03
      • 2021-08-07
      • 1970-01-01
      相关资源
      最近更新 更多