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

ReactJS:如何在Props值变更后仅执行一次useEffect

解决React useEffect重复调用Google Places API的问题

核心问题分析

你当前的useEffect以props.placeIds为依赖,但如果placeIds是父组件每次渲染时重新生成的数组(引用地址变化),哪怕内容没变,也会触发useEffect重复执行,导致大量API调用。直接用空数组依赖会在placeIds还未获取时就发起请求,引发崩溃。

解决方案

1. 增加条件判断,确保placeIds有效时才执行请求

在useEffect内部先判断placeIds是否存在且有内容,避免无效请求:

useEffect(() => {
    // 只有当placeIds存在且长度大于0时才执行请求
    if (!props.placeIds || props.placeIds.length === 0) return;

    fetch('/api/place-details-search', {
        method: 'POST',
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            placesId: props.placeIds,
            dayNumVal: props.dayNumVal,
            numOfPlaces: props.placeIds.length,
        })
    }).then(response => response.json()).then(data => {
        setPlaceHours(data.placeHourText)
    }).catch(err => {
        // 增加错误处理,避免前端崩溃
        console.error('获取营业时间失败:', err);
    })
}, [props.placeIds])

2. 使用useRef标记是否已执行过请求(针对引用类型依赖的重复触发)

如果父组件每次渲染都会生成新的placeIds数组(比如用setState直接创建新数组),即使内容相同,React也会认为依赖变化。这时可以用useRef记录是否已经处理过该组placeIds:

import { useEffect, useRef } from 'react';

// ...

const processedPlaceIds = useRef(new Set());

useEffect(() => {
    if (!props.placeIds || props.placeIds.length === 0) return;

    // 将placeIds转为字符串作为唯一标识,避免引用变化导致重复执行
    const idsKey = props.placeIds.join(',');
    if (processedPlaceIds.current.has(idsKey)) return;

    fetch('/api/place-details-search', {
        method: 'POST',
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            placesId: props.placeIds,
            dayNumVal: props.dayNumVal,
            numOfPlaces: props.placeIds.length,
        })
    }).then(response => response.json()).then(data => {
        setPlaceHours(data.placeHourText);
        // 标记该组ID已处理
        processedPlaceIds.current.add(idsKey);
    }).catch(err => {
        console.error('获取营业时间失败:', err);
    })
}, [props.placeIds])

3. 修复后端的变量名错误

你的后端代码中,req.body.placeId应该是req.body.placesIds(和前端传入的字段名一致),否则会导致placeIds为undefined,引发循环错误:

app.post('/api/place-details-search', (req, res) => {
    const dayNumVal = req.body.dayNumVal
    const numOfPlaces = req.body.numOfPlaces
    // 修复:将placeId改为placesIds,和前端传入的字段匹配
    const placeIds = req.body.placesIds
    let placeHourText = [];
    let placeHourName = [];
    let promises = [];
    let parsedDayNumVal = dayNumVal === 0 ? 6 : dayNumVal - 1;

    for (let i = 0; i < numOfPlaces; i++) {
        promises.push(
            axios.get(`https://maps.googleapis.com/maps/api/place/details/json?place_id=${placeIds[i]}&fields=name%2Copening_hours&key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}`)
                .then(response => {
                    const result = response.data.result;
                    if (result) {
                        // 增加可选链判断,避免opening_hours不存在时崩溃
                        placeHourText.push(result.opening_hours?.weekday_text[parsedDayNumVal] || '无营业时间数据');
                        placeHourName.push(result.name);
                    }
                })
        )
    }

    Promise.all(promises).then(() => {
        // 用map简化数组处理逻辑
        const placeHourTextTwo = placeHourText.map((text, index) => {
            const parsedPlaceHourText = text.split("y").pop();
            return [placeHourName[index], parsedPlaceHourText];
        });
        res.send({ placeHourText: placeHourTextTwo });
    }).catch(err => {
        // 增加错误处理,避免后端崩溃
        console.error('获取营业时间失败:', err);
        res.status(500).send({ error: '获取营业时间失败' });
    });
});

额外优化建议

  • 防抖/节流:如果用户操作会频繁触发placeIds变化,可以加防抖函数,避免短时间内多次请求
  • 缓存结果:在前端或后端对已查询过的placeId进行缓存,重复请求直接返回缓存数据,减少API调用次数
  • 加载状态:在前端增加加载状态提示,提升用户体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 18:36:42