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

React多端点异步获取API数据最佳实践及代码同步问题咨询

解决React异步数据加载与组件渲染同步问题

我正在做工作项目,学习React时卡在异步模型上。需求是先从数据库获取客户的name和token,再用每个token调用API获取对应客户的logo,最终在ImageList里展示,要求先渲染alt文本,等图片加载完成后再填充src。但useEffect里的异步操作同步有问题,用if控制执行逻辑感觉是临时方案,链式调用.then也因为setState异步导致竞态条件,没法正常工作。

原组件代码

const Customers = () => {
    const [logos, setLogos] = useState([]);
    const [customers, setCustomers] = useState([]);
    const [titlePrompt, setTitlePrompt] = useState([]);
    const navigate = useNavigate();

    useEffect(() => {
        // create a customer client object 
        const client = new Client();

        // query for the customer list and save name and token
        if (customers.length === 0) {
            client.get_customers().then((res) => {
                const customer_info = res.map(customer => {
                    return {
                        'name': customer.customer_name,
                        'token': customer.mqtt_customer_token
                    }
                });
                setCustomers(customer_info);
            });
        }

        if (customers.length > 0) {
            const fetched_logos = [];
            for (let i = 0; i < customers.length; i++) {
                client.get_site_by_type(customers[i].token, 1).then((res) => {
                    const new_logo = {
                        [customers[i].name]: res.logo 
                    };
                    fetched_logos.push(new_logo);
                });
            }
            setLogos(fetched_logos);
        }

        setTitlePrompt("Select A Customer Shown Below");
    }, [customers, logos]);

    /**
     * button handler to transition to a dataframe view of the chosen customer via token
     * @param {defining} token 
     */
    const onButtonClick = (token) => {
        navigate('/dataframe', { state: { token } });
    };

    return (
        <div className={'mainContainer'}>
            <div className={'contentContainer'}>
                <div className={'titleContainer'}>
                    <div>{titlePrompt}</div>
                </div>
                <br/>
                <ImageList cols={4}>
                    {customers.map(customer => (
                        <ImageListItem key={customer.name}>
                            <img alt={customer.name} src={`data:image/png;base64,${logos[customer.name]}`}></img>
                        </ImageListItem>
                    ))}
                </ImageList>
            </div>
        </div>
    );
};

export default Customers;

尝试过的链式调用代码片段

client.get_customers().then((res) => {
            const customer_info = res.map(customer => {
                return {
                    'name': customer.customer_name,
                    'token': customer.mqtt_customer_token
                }
            });
            setCustomers(customer_info);
        }).then(() => {
            for (let i = 0; i < customers.length; i++) {
                client.get_site_by_type(customers[i].token, 1).then((res) => {
                    const new_logo = {
                        [customers[i].name]: res[0].logo
                    }
                    setLogos([...logos, new_logo]);
                });
            }
        });

问题根源

  1. setState是异步操作,链式调用的第二个.then执行时,customers状态还未更新,导致循环无法获取有效数据
  2. 原useEffect依赖logos,每次setLogos都会触发重新执行,造成无限循环
  3. 批量请求logo时,直接push到数组再调用setLogos,但异步请求的完成顺序不确定,且fetched_logos会在所有请求完成前就被设置为初始空数组,导致状态更新无效

修复方案

1. 拆分useEffect,明确依赖范围

将客户列表加载和logo加载拆分为两个独立的useEffect,避免逻辑耦合:

  • 第一个useEffect仅负责加载客户列表,依赖为空数组(只执行一次)
  • 第二个useEffect监听customers变化,仅在客户列表加载完成后触发logo请求

2. 用Promise.all处理批量异步请求

通过Promise.all等待所有logo请求完成后,一次性更新状态,避免多次setState带来的竞态问题,同时将logos改为对象格式,方便通过客户名快速查找对应logo

3. 优化状态更新与渲染逻辑

移除不必要的依赖,避免无限循环;同时在渲染时判断logo是否加载完成,未完成时留空src,优先显示alt文本

修正后的完整代码

const Customers = () => {
    const [logos, setLogos] = useState({}); // 改为对象格式,便于按客户名映射logo
    const [customers, setCustomers] = useState([]);
    const [titlePrompt, setTitlePrompt] = useState("");
    const navigate = useNavigate();

    // 仅加载一次客户列表
    useEffect(() => {
        const client = new Client();
        client.get_customers().then((res) => {
            const customerInfo = res.map(customer => ({
                name: customer.customer_name,
                token: customer.mqtt_customer_token
            }));
            setCustomers(customerInfo);
            setTitlePrompt("Select A Customer Shown Below");
        });
    }, []);

    // 客户列表更新后,批量加载logo
    useEffect(() => {
        if (customers.length === 0) return;

        const client = new Client();
        // 为每个客户创建logo请求Promise
        const logoRequests = customers.map(customer => 
            client.get_site_by_type(customer.token, 1)
                .then(res => ({ [customer.name]: res.logo }))
        );

        // 等待所有请求完成后,合并结果并更新状态
        Promise.all(logoRequests).then(logoResults => {
            const logoObj = logoResults.reduce((acc, curr) => ({ ...acc, ...curr }), {});
            setLogos(logoObj);
        });
    }, [customers]);

    const onButtonClick = (token) => {
        navigate('/dataframe', { state: { token } });
    };

    return (
        <div className={'mainContainer'}>
            <div className={'contentContainer'}>
                <div className={'titleContainer'}>
                    <div>{titlePrompt}</div>
                </div>
                <br/>
                <ImageList cols={4}>
                    {customers.map(customer => (
                        <ImageListItem key={customer.name}>
                            <img 
                                alt={customer.name} 
                                src={logos[customer.name] ? `data:image/png;base64,${logos[customer.name]}` : ""}
                            />
                        </ImageListItem>
                    ))}
                </ImageList>
            </div>
        </div>
    );
};

export default Customers;

关键优化说明

  • 状态结构优化:logos从数组改为对象,直接通过客户名关联logo,避免数组查找的复杂度
  • 异步请求管理:Promise.all确保所有logo请求完成后再更新状态,保证数据完整性,同时减少状态更新次数
  • 依赖控制:拆分后的useEffect依赖清晰,避免不必要的重复执行和无限循环
  • 渲染体验优化:img的src属性做了条件判断,logo未加载时显示alt文本,加载完成后自动填充图片,符合需求

内容的提问来源于stack exchange,提问作者Logan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 20:25:58