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

React Hook调用async/await获取排班数据先返回undefined的问题

问题描述

在React自定义Hook中调用异步方法获取排班数据时,执行console.log(roster.rosterRow)首次返回undefined,一段时间后才返回预期值,roster.rosterRow = await fetchAPI.getRoster(year, month)的await看似未生效,求解决方法。


相关代码

RosterDataUtil.js

import FetchAPI from "./FetchAPI";
import { Utility } from "./Utility";
export default class RosterDataUtil {
    constructor(){
        let fetchAPI = new FetchAPI();
        let roster = null;
        ............
        this.init = async (weekdayNames) => {
            roster = { activeShiftList: await fetchAPI.getActiveShiftList()};
            roster.weekdayNames = weekdayNames;
        }
        this.loadData = async (year, month, noOfWorkingDay) => {
            roster.year = year;
            roster.month = month;
            roster.rosterRow = await fetchAPI.getRoster(year, month);
            console.log(roster.rosterRow);
        }
        ...........................
    }       
}

FetchAPI.js

import axios from "axios";
export default class FetchAPI {
    constructor() {
        this.getRoster = async (year, month) => {
            return (await fetch({ year: year, month: month }, "get", "/rosterWeb/publicAPI/getRoster"));
        }
        //================================================================================================================
        // create and configure an axios instance
        const api = axios.create({
            baseURL: process.env.REACT_APP_SOCKET_URL,
        });
        // add the response interceptor
        api.interceptors.response.use(
            null, // default success handler
            (error) => {
                console.log(error.toJSON());
                return Promise.reject({
                    status: error.response?.status,
                    message:
                        error.response?.data ?? error.response?.statusText ?? error.message,
                });
            },
            {
                synchronous: true, // optimise interceptor handling
            }
        );

        const fetch = async (data, method, url, responseType, headers) => {
            const requestObj = {
                url,
                method,
                responseType,
                headers,
                [method.toLowerCase() === "get" ? "params" : "data"]: data,
            };
            const response = await api(requestObj); // use the created instance
            if (response.request.responseType === "blob") {
                let fileName = response.headers["content-disposition"];
                console.log(fileName);
                let firstIndex = fileName.indexOf("filename=");
                fileName = fileName.substring(firstIndex + 9);

                const newBlob = new Blob([response.data]);
                const objUrl = window.URL.createObjectURL(newBlob);
                const link = document.createElement("a");
                link.href = objUrl;
                link.download = fileName;
                link.click();
            }
            return response.data;
        };
    }
}

自定义Hook代码

import { useEffect, useReducer } from "react";
import CalendarUtility from "../../util/calendar/CalendarUtility";
import RosterDataUtil from "./RosterDataUtill";

let reducer = (state, action) => {
    let result = { ...state };
    switch (action.type) {
        case "init":
            result.monthlyCalendar = action.monthlyCalendar;
            result.roster = result.rosterDataUtil.getRoster();           
            result.isLoading = false;
            break;
        case "setError":
            result.error = action.error;
            break;
        default:
            break;
    }
    return result;
}
export function useTestRoster() {
    const [itemList, updateItemList] = useReducer(reducer, {
        calendarUtility: new CalendarUtility(),
        monthlyCalendar: null,
        error: null,
        isLoading: true,
        roster: null,
        rosterDataUtil: new RosterDataUtil(),
        systemParam: null,
    });
    useEffect(() => {
        let systemUtil = new SystemUtil();
        let getData = async () => {
            try{
                let now = new Date();
                let rosterYear = now.getFullYear();
                let rosterMonth = now.getMonth();
                let monthlyCalendar = itemList.calendarUtility.getMonthlyCalendar(rosterYear, rosterMonth);
                await itemList.rosterDataUtil.init(itemList.calendarUtility.weekdayNames);
                await itemList.rosterDataUtil.loadData(rosterYear, rosterMonth + 1, monthlyCalendar.noOfWorkingDay);
                updateItemList({
                    monthlyCalendar,
                    type: "init"
                });
            }catch (error){
                console.log(error);
                updateItemList({ "error": error, "type": "setError" });
            }
        }
        getData();
    }, []);
    
    return {
        error: itemList.error,
        isLoading: itemList.isLoading,
        roster: itemList.roster,

    }
}

问题根源

  1. FetchAPI作用域错误:自定义的fetch函数是constructor内部局部变量,this.getRoster无法访问它,实际调用了浏览器全局fetch,导致请求格式错误,返回undefined。
  2. RosterDataUtil数据未同步:内部roster是局部变量,且未暴露获取方法,组件state无法拿到更新后的数据;同时reducer中调用了不存在的getRoster方法,导致state里的roster始终为undefined。
  3. 异步数据未正确同步到state:loadData完成后,没有将最新的roster传递给reducer更新组件状态。

解决步骤

1. 修复FetchAPI的作用域问题

将自定义fetch挂载到实例上,确保getRoster能调用到正确的请求函数:

import axios from "axios";
export default class FetchAPI {
    constructor() {
        // 创建axios实例
        const api = axios.create({
            baseURL: process.env.REACT_APP_SOCKET_URL,
        });
        // 响应拦截器
        api.interceptors.response.use(
            null,
            (error) => {
                console.log(error.toJSON());
                return Promise.reject({
                    status: error.response?.status,
                    message: error.response?.data ?? error.response?.statusText ?? error.message,
                });
            },
            { synchronous: true }
        );

        // 将fetch挂载到实例
        this.fetch = async (data, method, url, responseType, headers) => {
            const requestObj = {
                url,
                method,
                responseType,
                headers,
                [method.toLowerCase() === "get" ? "params" : "data"]: data,
            };
            const response = await api(requestObj);
            if (response.request.responseType === "blob") {
                let fileName = response.headers["content-disposition"];
                let firstIndex = fileName.indexOf("filename=");
                fileName = fileName.substring(firstIndex + 9);
                const newBlob = new Blob([response.data]);
                const objUrl = window.URL.createObjectURL(newBlob);
                const link = document.createElement("a");
                link.href = objUrl;
                link.download = fileName;
                link.click();
            }
            return response.data;
        };

        // 使用this.fetch调用自定义请求函数
        this.getRoster = async (year, month) => {
            return await this.fetch({ year, month }, "get", "/rosterWeb/publicAPI/getRoster");
        };

        // 补充getActiveShiftList实现
        this.getActiveShiftList = async () => {
            return await this.fetch(null, "get", "/rosterWeb/publicAPI/getActiveShiftList");
        };
    }
}

2. 修复RosterDataUtil的数据访问问题

将roster改为实例属性,并添加getRoster方法暴露数据:

import FetchAPI from "./FetchAPI";
import { Utility } from "./Utility";
export default class RosterDataUtil {
    constructor(){
        this.fetchAPI = new FetchAPI();
        this.roster = null; // 改为实例属性

        this.init = async (weekdayNames) => {
            this.roster = { activeShiftList: await this.fetchAPI.getActiveShiftList()};
            this.roster.weekdayNames = weekdayNames;
        };

        this.loadData = async (year, month, noOfWorkingDay) => {
            if (!this.roster) throw new Error("请先调用init初始化roster");
            this.roster.year = year;
            this.roster.month = month;
            this.roster.rosterRow = await this.fetchAPI.getRoster(year, month);
            console.log(this.roster.rosterRow); // 现在会正确返回数据
        };

        // 添加getRoster方法返回当前数据
        this.getRoster = () => this.roster;
    }       
}

3. 修复自定义Hook的state同步逻辑

在loadData完成后,将最新的roster传递给reducer更新state:

import { useEffect, useReducer } from "react";
import CalendarUtility from "../../util/calendar/CalendarUtility";
import RosterDataUtil from "./RosterDataUtill";
import SystemUtil from "./SystemUtil"; // 补充缺失的导入

let reducer = (state, action) => {
    let result = { ...state };
    switch (action.type) {
        case "init":
            result.monthlyCalendar = action.monthlyCalendar;
            result.roster = action.roster; // 直接从action接收更新后的roster
            result.isLoading = false;
            break;
        case "setError":
            result.error = action.error;
            result.isLoading = false;
            break;
        default:
            break;
    }
    return result;
};

export function useTestRoster() {
    const [itemList, updateItemList] = useReducer(reducer, {
        calendarUtility: new CalendarUtility(),
        monthlyCalendar: null,
        error: null,
        isLoading: true,
        roster: null,
        rosterDataUtil: new RosterDataUtil(),
        systemParam: null,
    });

    useEffect(() => {
        const getData = async () => {
            try{
                const now = new Date();
                const rosterYear = now.getFullYear();
                const rosterMonth = now.getMonth();
                const monthlyCalendar = itemList.calendarUtility.getMonthlyCalendar(rosterYear, rosterMonth);
                
                await itemList.rosterDataUtil.init(itemList.calendarUtility.weekdayNames);
                await itemList.rosterDataUtil.loadData(rosterYear, rosterMonth + 1, monthlyCalendar.noOfWorkingDay);
                
                // 获取最新roster并更新state
                const updatedRoster = itemList.rosterDataUtil.getRoster();
                updateItemList({
                    monthlyCalendar,
                    roster: updatedRoster,
                    type: "init"
                });
            }catch (error){
                console.log(error);
                updateItemList({ error, type: "setError" });
            }
        };
        getData();
    }, []);
    
    return {
        error: itemList.error,
        isLoading: itemList.isLoading,
        roster: itemList.roster,
    };
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 12:22:02