问题描述
完全是 React 新手,我正在构建一个基于 Pokeapi 的小型 Pokedex。我能够获取 Pokemon 及其相应的数据,并成功渲染它。
我有一个下拉菜单,用户可以在其中选择 Pokemon 范围(开始和结束编号),并且 Pokemon 列表应该会自动更新。问题是,前一个范围没有被清除,当前范围与前一个范围相加。但是当我允许一个范围内的所有口袋妖怪完全加载并选择第二个范围时,我没有遇到这个错误。在范围之间快速切换会导致此错误。下面的代码片段。
问题是当我选择一个下拉项时 allPokemons 被设置为空,但在渲染中没有反映出来
GIF 演示我的问题:Pokedex error
状态:
constructor(props) {
super(props);
this.state = {
allPokemons: [],limit: 151,offset: 0,regions: [
{
name: "Kanto",},{
name: "Johto",limit: 100,offset: 151,]
}
}
获取口袋妖怪列表和相应数据:
getAllPokemons = async () => {
const response = await axios.get(`https://pokeapi.co/api/v2/pokemon?limit=${this.state.limit}&offset=${this.state.offset}`).catch((err) => console.log("Error:",err));
this.getPokemonData(response.data.results);
}
getPokemonData = async (result) => {
this.setState({
allPokemons : [],})
var response;
for (var i = 0; i < result.length; i++) {
response = await axios.get(`https://pokeapi.co/api/v2/pokemon/${result[i].name}`).catch((err) => console.log("Error:",err));
this.setState({
allPokemons: [...this.state.allPokemons,response.data],})
}
}
下拉句柄更改功能
handleChangeRegions = (event) => {
this.setState({
allPokemons : [],})
for (var i = 0; i < this.state.regions.length; i++) {
if (this.state.regions[i].name === event.target.value) {
this.setState({
limit : this.state.regions[i].limit,offset : this.state.regions[i].offset,allPokemons : [],()=>{
this.getAllPokemons();
})
break;
}
}
}
渲染组件
Object.keys(this.state.allPokemons).map((item,index) =>
<Pokemon
key={index}
id={this.state.allPokemons[item].id}
name={this.state.allPokemons[item].name}
type={this.state.allPokemons[item].types}
/>
)
下拉组件
<select value={this.state.valueregion} onChange={this.handleChangeRegions}>
{this.state.regions.map((region) => (
<option value={region.name}>{region.name} ({region.offset + 1}-{region.limit + region.offset})</option>
))}
</select>
我需要知道这个问题的原因,非常感谢解决方案!
解决方法
还有几个注意事项,
- 在您命名的
Render Component
中,将键设置为数组的索引是一种非常糟糕的做法,这将导致呈现的项目出现问题,尤其是 onclick 处理程序等,请使用唯一的id 从 api 响应返回,因为您在 id 中使用它(不确定为什么要使用 id,但您需要设置密钥),更多信息 here - 您经常使用
setState
,在handleChangeRegions
中,您重置allPokemons
键的状态,然后使用下拉列表中的值再次设置它,而您只需从下拉到下一个函数,这将执行 api 调用,您只需要在屏幕上发生变化时才需要使用状态 一个变化的变量应该会导致您的组件重新渲染 - 此外,您每次都会使用新响应设置状态,从而导致不必要的重新渲染,只需将 api 响应中的内容保存在一个数组中,然后在所有请求都通过后将其传递给状态立>
这可能不是解决方案,但它至少应该给您一些提示来缩小问题的范围。如果您可以在代码沙箱中重新生成代码,这将非常有助于找出问题而不是完整代码,但您可以替换 Pokemon 组件,例如显示 id 以方便我们调试代码。>
编辑
由于问题现已更新并且原始问题已解决,因此我检查了您的沙箱,您正在发出单独的请求,每个请求都在等待之前的请求
for (var i = 0; i < result.length; i++) {
response = await axios
.get(`https://pokeapi.co/api/v2/pokemon/${result[i].name}`)
.catch((err) => console.log("Error:",err));
pokemonArr.push(response.data);
}
这就是你的罪魁祸首,每个请求都被发送,然后你正在等待为每个 pokemon 处理该请求......你基本上发送了 200 个请求,但不是并行而是顺序发送,因为你使用的是异步等待。
将该部分替换为以下内容
await Promise.all(
result.map(pokemonItem => {
return axios.get(`https://pokeapi.co/api/v2/pokemon/${pokemonItem.name}`)
.then(result => {
pokemonArr.push(result.data);
});
})
);
this.setState({
allPokemons:pokemonArr
})
Promise.all()
将所有请求一起发送,并与之前的 await
语句一起处理它们的响应,只等待所有请求完成后再设置状态,瞧!就像一个魅力。
现在这里还有一些评论:
- 通常您在
componentDidMount()
生命周期挂钩中发送 api 调用,而不是在comopnentWillMount()
- 但我想你已经知道了,发送 200 个请求来访问每个单独的 pokemon 的数据并将它们显示在列表中对于后端服务来说是一个糟糕的设计......我知道它不是你的,而是像这样的案例你赢了通常不会在现实生活中见面。
现在您可能会发现唯一奇怪的是您的列表不会被排序..因为很多请求是并行发送的,我们不知道哪个请求将首先被处理..一个简单的排序函数可以解决这个问题你可以找到一个here
我已经用有效的解决方案更新了您的代码 here 快乐黑客,欢迎来到 SO!