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

React useEffect内forEach循环未执行及fetch异步问题排查

useEffect内部forEach循环未进入执行问题解答

问题根因

forEach未执行的本质是两个问题叠加:

  1. 闭包旧值问题:第二个fetch的then回调中读取的updatedRides,是useEffect首次执行时闭包保存的初始值——也就是空数组[]。React的state更新是异步的,setUpdatedRides触发的重渲染不会修改当前已经在执行的useEffect回调里的变量值,空数组调用forEach自然不会进入循环体。
  2. 请求时序不可控:两个平级编写的fetch请求是并发发起的,没有先后等待关系,哪个请求先返回完全取决于网络速度。如果用户信息接口比行程接口先返回,此时行程数据还未获取到,updatedRides必然是空值。

疑问答复

  • 关于「是否可以在fetch的then回调中调用自定义函数」:完全可以。then回调本质是普通的JavaScript异步回调函数,只要自定义函数在当前作用域可访问,就可以正常调用,你代码中calculateDistance的调用本身不存在语法问题。
  • 关于「两个fetch的执行顺序以及如何实现顺序执行」:
    你当前写法下两个fetch是并发执行的,调用fetch方法后请求会立刻发出,不会等待前一个请求返回再发起下一个。
    要实现useEffect内逻辑按顺序执行,常用两种方案:
    • 方案1:Promise链式调用,把第二个fetch放到第一个fetch的then回调中,等前一个请求拿到结果后再发起下一个请求
    • 方案2:使用async/await语法(可读性更高),在useEffect内定义异步函数,按顺序await两个请求的返回结果,拿到所有需要的数据后再做遍历计算
      注意:不要在setState调用之后,立刻在当前回调中读取刚更新的state做计算,直接使用接口返回的原始数据计算即可,从根源上避免闭包拿到旧state值的问题。

修正后可运行代码

import { useEffect, useState } from "react";
import Navbar from "./Navbar";
import RideDetails from "./RideDetails";

const Rides = () => {
  const [rides, setRides] = useState([]);
  const [user, setUser] = useState({});
  const [updatedRides, setUpdatedRides] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      try {
        // 先请求行程数据
        const ridesRes = await fetch('https://assessment.api.vweb.app/rides', {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json'
          } 
        });
        if(!ridesRes.ok) throw new Error('Unable to fetch Rides data');
        const ridesResult = await ridesRes.json();

        // 行程数据返回后再请求用户数据
        const userRes = await fetch('https://assessment.api.vweb.app/user', {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json'
          }
        });
        if(!userRes.ok) throw new Error('Unable to fetch User');
        const userResult = await userRes.json();

        // 直接用接口返回的原始数据计算,不依赖闭包中的state
        const processedRides = ridesResult.map(ride => {
          return {
            ...ride,
            distance: calculateDistance(ride.station_path, userResult.station_code)
          }
        });

        // 统一更新所有state
        setRides(ridesResult);
        setUser(userResult);
        setUpdatedRides(processedRides);
      } catch (e) {
        console.log(e);
      }
    }

    fetchData();
  }, [])

  function calculateDistance (path, user_station) {
    let min = Math.abs(user_station - path[0]);
    for(let i = 0; i<path.length; i++){
      if(path[i] === user_station){
        return 0;
      }
      const currentDistance = Math.abs(path[i] - user_station);
      if(currentDistance < min){
        min = currentDistance;
      }
    }
    return min;
  }

  return(
    <div>
      <Navbar user = {user}/>
      <div className="rides">
        {updatedRides.map((ride) => {
          return (
            <div className="rideDetail" key={ride.id}>
              <img src = {ride.map_url} alt="Ride_map" />
              <div>
                <p>Ride Id : {ride.id}</p>
                <p>Origin Station : {ride.origin_station_code}</p>
                <p>Station Path : {ride.station_path}</p>
                <p>Date : {ride.date}</p>
                <p>Distance : {ride.distance}</p>
              </div>
            </div>
          )
        })}
      </div>
    </div>
  )
}

export default Rides;

修正点说明

  • 用async/await保证请求按顺序执行,等两个接口数据都返回后再做计算
  • 距离计算直接使用接口返回的原始数据,完全规避闭包读取旧state的问题
  • 遍历行程数据时用map生成新数组,遵循React不可变数据更新原则,不直接修改原接口返回的对象属性
  • 给列表渲染项增加key={ride.id},消除React列表渲染的警告

内容的提问来源于stack exchange,提问作者Anuj kumar Jaimini

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 14:09:25