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
相关产品推荐
相关产品推荐

