React中多次API调用后如何等待UI更新?是否可使用useEffect实现?
问题解决方法
你当前需要点击三次才能加载完整数据的核心原因是React状态更新为异步操作:调用setPokemon后pokemon状态不会立刻更新,你在后续请求中直接读取pokemon.id、pokemonDescription.evolution拿到的都是上一次渲染的旧值,所以前两次点击参数都是空的,第三次才能拿到正确参数发起请求。
两种可行实现方案如下:
方案1:直接链式调用请求(更简洁,无需useEffect)
直接复用前一次请求返回的原始数据作为下一次请求的参数,不依赖state传参,一次点击即可跑完所有请求:
import Axios from "axios"; import React, { useState } from "react"; // axios实例移到组件外,避免每次渲染重复创建 const api = Axios.create({ baseURL: "https://pokeapi.co/api/v2/", }); function PK() { const [pokemon, setPokemon] = useState({}); const [pokemonDescription, setPokemonDescription] = useState({}); const [evolution, setEvolution] = useState({}); const [loading, setLoading] = useState(false); // 加载状态提升用户体验 const searchPokemon = async () => { try { setLoading(true); // 第一步:请求基础宝可梦数据 const pokemonRes = await api.get(`pokemon/magnemite`); const basePokemon = { name: pokemonRes.data.name, height: pokemonRes.data.height, weight: pokemonRes.data.weight, img: pokemonRes.data.sprites.front_default, id: pokemonRes.data.id, type: pokemonRes.data.types[0].type.name, // 加可选链避免单属性宝可梦报错 type2: pokemonRes.data.types[1]?.type.name || '', }; // 第二步:直接用第一步返回的id发请求,不用等state更新 const speciesRes = await api.get(`pokemon-species/${basePokemon.id}/`); const description = { entry: speciesRes.data.flavor_text_entries[0].flavor_text, evolutionUrl: speciesRes.data.evolution_chain.url, }; // 第三步:直接用第二步返回的进化链url发请求 const evolutionRes = await api.get(description.evolutionUrl); const evolutionData = { // 加可选链避免最终形态宝可梦无进化链报错 evolution: evolutionRes.data.chain.evolves_to[0]?.species.name || '无进化', }; // 统一更新所有状态,也可每步请求完成后单独更新 setPokemon(basePokemon); setPokemonDescription(description); setEvolution(evolutionData); } catch (err) { console.error('请求失败:', err); } finally { setLoading(false); } }; return ( <div> <h1 style={{ textTransform: "capitalize" }}>{pokemon.name}</h1> <h1>#{pokemon.id}</h1> <h1>{pokemon.weight / 10} Kg</h1> <h1>{pokemon.height * 10} Cm</h1> <img src={pokemon.img} alt={pokemon.name} /> <h1 style={{ textTransform: "capitalize" }}> Type: {pokemon.type} {pokemon.type2} </h1> <h2 style={{ textTransform: "capitalize" }}> {pokemonDescription.entry} </h2> <h1 style={{ textTransform: "capitalize" }}> Evolution: {evolution.evolution} </h1> <button onClick={searchPokemon} disabled={loading}> {loading ? '加载中...' : '点击加载宝可梦信息'} </button> </div> ); } export default PK;
方案2:使用useEffect监听状态触发后续请求
如果你需要用useEffect实现,可以监听前一步返回的状态作为依赖,自动触发后续请求:
import Axios from "axios"; import React, { useState, useEffect } from "react"; const api = Axios.create({ baseURL: "https://pokeapi.co/api/v2/", }); function PK() { const [pokemon, setPokemon] = useState({}); const [pokemonDescription, setPokemonDescription] = useState({}); const [evolution, setEvolution] = useState({}); const [triggerFetch, setTriggerFetch] = useState(false); // 点击按钮触发首次请求 // 第一步:监听触发状态,请求基础宝可梦数据 useEffect(() => { if (!triggerFetch) return; api.get(`pokemon/magnemite`).then((response) => { setPokemon({ name: response.data.name, height: response.data.height, weight: response.data.weight, img: response.data.sprites.front_default, id: response.data.id, type: response.data.types[0].type.name, type2: response.data.types[1]?.type.name || '', }); }); }, [triggerFetch]); // 第二步:监听pokemon.id变化,请求物种描述数据 useEffect(() => { if (!pokemon.id) return; api.get(`pokemon-species/${pokemon.id}/`).then((response) => { setPokemonDescription({ entry: response.data.flavor_text_entries[0].flavor_text, evolutionUrl: response.data.evolution_chain.url, }); }); }, [pokemon.id]); // 第三步:监听evolutionUrl变化,请求进化链数据 useEffect(() => { if (!pokemonDescription.evolutionUrl) return; api.get(pokemonDescription.evolutionUrl).then((response) => { setEvolution({ evolution: response.data.chain.evolves_to[0]?.species.name || '无进化', }); }); }, [pokemonDescription.evolutionUrl]); return ( <div> <h1 style={{ textTransform: "capitalize" }}>{pokemon.name}</h1> <h1>#{pokemon.id}</h1> <h1>{pokemon.weight / 10} Kg</h1> <h1>{pokemon.height * 10} Cm</h1> <img src={pokemon.img} alt={pokemon.name} /> <h1 style={{ textTransform: "capitalize" }}> Type: {pokemon.type} {pokemon.type2} </h1> <h2 style={{ textTransform: "capitalize" }}> {pokemonDescription.entry} </h2> <h1 style={{ textTransform: "capitalize" }}> Evolution: {evolution.evolution} </h1> <button onClick={() => setTriggerFetch(true)}>点击加载宝可梦信息</button> </div> ); } export default PK;
内容的提问来源于stack exchange,提问作者user14396526
相关产品推荐
相关产品推荐

