我是React和JS的新手,并且仍然在学习,我试图创建一个网站来显示SWAPI API中的数据,尤其是People数据。如果我获取人员的第一页,则存在一个名为“ 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
我认为在return字段中添加状态更改就足够了,但是还不够,所以我尝试添加componentDidUpdate函数来尝试触发状态更改,但是我认为这样做没有帮助。我还很好奇,当尚未进行任何更新时,如何首先显示来自componentDidUpdate的日志?
目前,所有这些操作都是将相同的4页连接到people数组中(控制台对此有所抱怨)。
所以我的问题是,如何使用上一次获取的数据正确设置url状态?
编辑:好的,我忘记添加到我的问题中的一件事是,我正计划使此访存泛型化,以便它可以接受SWAPI上的任何类别,并使用“下一个”字段来确定何时停止访存数据。我确实有一段类似于Yousafs答案的代码,我获取了第一页,然后使用计数循环遍历所有单独的页面,但这意味着要进行72次获取!那是浏览页面似乎更好的选择。我现在有个更好的主意。
答案 0 :(得分:1)
fetch()
是asynchronous。当您调用fetch
时,它会立即返回Promise
并安排实际的获取以稍后执行。它实际上并没有像您以前使用其他语言/框架那样直接进行内联工作。
因此,您的for
循环从0开始,调用fetch
,该{<1>安排使用当前URL进行抓取(但实际上并未执行抓取),因此变为1计划使用相同的URL进行抓取(请记住,由于之前的抓取尚未实际执行,所以它没有更改),等等。
一旦您的for
循环和回调函数退出,fetch
便会执行。现在,在React中,每次更新状态时,组件都会重新渲染。这是非常昂贵的计算,因此React尝试尽可能少地执行它。 React进行的一种优化是{{3}},以便将一连串的状态更改汇总为一个大的状态更改。您只会从componentDidUpdate
看到一个日志,因为它实际上只更新了一次。
有几种方法可以解决此问题。我的建议是利用JavaScript的makes state changes asynchronous语法。将async
放在原始setState
回调的箭头函数之前,并将await
放在fetch
之前。
答案 1 :(得分:1)
您在循环中获取数据的方法是完全错误的。 fetch
函数返回一个Promise,并且实际请求是异步进行的。您应该做的是将fetch
函数返回的所有promise保存在一个数组中,然后使用Promise.all()函数解析所有这些promise。
要获取所需的数据,请按照以下步骤操作:
在componentDidMount
函数中,创建一个数组,该数组将保存fetch
函数返回的所有promise。创建一个循环,并将fetch
函数返回的所有promise添加到先前创建的中。
然后,调用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页的数据并显示所有人的姓名。