You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React调用星球大战API时useEffect效率优化与状态设置问题

问题根源

当前代码存在几个核心错误导致数据提前渲染:

  • 调用fetch时没有返回Promise实例,Promise.all实际接收到的是一堆undefined,根本没有等待网络请求完成就执行了后续的赋值操作
  • 缺少SWAPI分页拉取逻辑,目前仅能拉取第一页的10个角色数据
  • 直接修改接口返回的原始对象,违反React不可变数据原则,容易触发意外渲染问题
  • 没有等待全量角色的所有关联数据拉取完成,就提前更新了characters状态

修正方案

完整逻辑代码

const api = `https://swapi.dev/api/people/`;
const [characters, setCharacters] = useState([]);
const [speciesOptions, setSpeciesOptions] = useState([]);
const [homeworldOptions, setHomeworldOptions] = useState([]);
const [fetched, setFetched] = useState(false);

useEffect(() => {
    // 分页拉取所有角色基础数据
    const fetchAllCharacters = async () => {
        let allCharacters = [];
        let nextUrl = api;
        while (nextUrl) {
            const res = await fetch(nextUrl);
            const data = await res.json();
            allCharacters.push(...data.results);
            nextUrl = data.next;
        }
        return allCharacters;
    };

    // 拉取单个角色的所有关联数据,返回处理完成的新角色对象
    const processCharacter = async (rawCharacter) => {
        // 浅拷贝原对象,不修改原始数据
        const processedCharacter = { ...rawCharacter };
        // 收集所有需要拉取的请求任务
        const fetchTasks = [];

        // 母星处理
        fetchTasks.push(
            fetch(processedCharacter.homeworld)
                .then(res => res.json())
                .then(data => {
                    processedCharacter.homeworld = data.name;
                })
        );

        // 物种处理
        if (processedCharacter.species.length) {
            processedCharacter.species.forEach(speciesUrl => {
                fetchTasks.push(
                    fetch(speciesUrl)
                        .then(res => res.json())
                        .then(data => {
                            processedCharacter.species = processedCharacter.species.map(u => u === speciesUrl ? data.name : u);
                        })
                );
            });
        } else {
            processedCharacter.species = ["Unspecified"];
        }

        // 载具处理
        if (processedCharacter.vehicles.length) {
            processedCharacter.vehicles.forEach(vehicleUrl => {
                fetchTasks.push(
                    fetch(vehicleUrl)
                        .then(res => res.json())
                        .then(data => {
                            processedCharacter.vehicles = processedCharacter.vehicles.map(u => u === vehicleUrl ? data.name : u);
                        })
                );
            });
        }

        // 星舰处理
        if (processedCharacter.starships.length) {
            processedCharacter.starships.forEach(starshipUrl => {
                fetchTasks.push(
                    fetch(starshipUrl)
                        .then(res => res.json())
                        .then(data => {
                            processedCharacter.starships = processedCharacter.starships.map(u => u === starshipUrl ? data.name : u);
                        })
                );
            });
        }

        // 等待当前角色所有关联请求完成
        await Promise.all(fetchTasks);
        return processedCharacter;
    };

    // 主流程
    const initData = async () => {
        try {
            // 1. 拉取全量角色基础数据
            const rawCharacters = await fetchAllCharacters();
            // 2. 并行处理所有角色的关联数据拉取
            const processedCharacters = await Promise.all(
                rawCharacters.map(char => processCharacter(char))
            );
            // 3. 生成筛选选项
            const speciesSet = new Set(processedCharacters.flatMap(c => c.species));
            setSpeciesOptions(Array.from(speciesSet).map(s => <option value={s} key={s}>{s}</option>));
            
            const homeworldSet = new Set(processedCharacters.map(c => c.homeworld));
            setHomeworldOptions(Array.from(homeworldSet).map(h => <option value={h} key={h}>{h}</option>));
            // 4. 统一更新状态,所有数据就绪后再标记为加载完成
            setCharacters(processedCharacters);
            setFetched(true);
        } catch (err) {
            console.error("数据拉取失败:", err);
        }
    };

    initData();
}, []);

渲染层适配

加载完成前只显示占位符,完全避免原始URL暴露:

if (!fetched) return <div>数据加载中,请稍候...</div>;

// 加载完成后再渲染完整页面
return (
    <div>
        <select>{speciesOptions}</select>
        <select>{homeworldOptions}</select>
        <table>
            {/* 渲染处理完成的characters数据即可 */}
        </table>
    </div>
)

核心优化点

  • 所有异步请求都返回Promise,通过多层Promise.all确保全量数据全部处理完成后才更新状态,不会出现部分数据先渲染的问题
  • 所有对象修改都基于浅拷贝,不修改原始数据,避免React状态更新异常
  • 分页逻辑用while循环实现,更简洁易维护
  • 筛选选项在数据处理完成后直接生成,不需要单独写函数监听state变化,减少不必要的重渲染

内容的提问来源于stack exchange,提问作者Bartosz Król

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 15:27:00