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, } }
问题根源
- FetchAPI作用域错误:自定义的
fetch函数是constructor内部局部变量,this.getRoster无法访问它,实际调用了浏览器全局fetch,导致请求格式错误,返回undefined。 - RosterDataUtil数据未同步:内部
roster是局部变量,且未暴露获取方法,组件state无法拿到更新后的数据;同时reducer中调用了不存在的getRoster方法,导致state里的roster始终为undefined。 - 异步数据未正确同步到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
相关产品推荐
相关产品推荐

