React Context值在useEffect中未更新及请求循环问题咨询
React+TypeScript开发中的两个问题解析
问题现象
- 在useEffect中调用
setInitialData更新状态后,立即console.log(initialData)输出的仍是null - 代码持续发起HTTP请求,陷入无限循环
代码片段
Context代码(InitialDataContext.tsx)
import React, { Dispatch, SetStateAction, createContext, useContext, useState, } from 'react'; interface InitialDataType { GameName: string; GameUrl: string; HasRegulation: boolean; HideNavBar: boolean; HideSessionNetLoss: boolean; HideSessionTimer: boolean; RegulationAction: string; RegulationModel?: string; TournamentId?: number; TournamentType?: number; TournamentsBaseUrl?: string; Url: string; VendorController: string; } interface InitialDataContextInterface { initialData: InitialDataType | null; setInitialData: Dispatch<SetStateAction<InitialDataType | null>>; } const Context = createContext({} as InitialDataContextInterface); const InitialDataProvider = ({ children }) => { const [initialData, setInitialData] = useState<InitialDataType | null>( null ); return ( // eslint-disable-next-line react/jsx-no-constructed-context-values <Context.Provider value={{ initialData, setInitialData }}> {children} </Context.Provider> ); }; export default InitialDataProvider; export const useInitialData = () => useContext(Context);
Home组件代码(Home.tsx)
import React, { useContext, useEffect } from 'react'; import axios from 'axios'; import UkRegulation from '../../regulations/UkRegulation/UkRegulation'; import GameIframe from '../../components/GameIframe/GameIframe'; import { useInitialData } from '../../context/InitialDataContext'; const Home = () => { const { initialData, setInitialData } = useInitialData(); useEffect(() => { axios .get( 'https://myUrl.com/my-endpoint' ) .then((response) => { setInitialData(response.data); console.log('InitialData', initialData); }) .catch((error) => { console.log(error); }); }, []); return ( <div> <UkRegulation /> <GameIframe /> </div> ); }; export default Home;
问题原因
1. console.log输出null的原因
React的useState状态更新是异步的,且闭包特性会让useEffect回调函数捕获组件首次渲染时的initialData值(也就是初始的null)。即便调用setInitialData触发了状态更新,当前回调里的initialData并不会立即改变——它仍然是闭包中保存的旧值,所以输出null。
2. 无限循环请求的原因
你的InitialDataProvider组件每次渲染时,都会创建一个全新的{ initialData, setInitialData }对象作为Provider的value。当initialData更新后,Provider会重新渲染,导致value引用变化,所有消费该Context的组件(包括Home)都会被迫重新渲染。如果Home的子组件(如UkRegulation或GameIframe)中也存在发起请求的逻辑,就会形成循环请求;另外,若不慎将initialData或setInitialData加入useEffect依赖数组,也会触发重复执行,但你的代码中依赖为空数组,所以核心原因是Context的value引用不稳定导致的频繁组件渲染。
解决方案
1. 查看更新后的initialData值
有两种方式可以获取到最新的状态:
- 方式一:在组件顶级作用域打印:组件每次重新渲染都会执行这里的代码,能输出最新的
initialData
const Home = () => { const { initialData, setInitialData } = useInitialData(); console.log('最新InitialData:', initialData); // 这里会输出更新后的值 useEffect(() => { axios.get('https://myUrl.com/my-endpoint') .then(res => setInitialData(res.data)) .catch(err => console.log(err)); }, []); // ... };
- 方式二:用useEffect监听状态变化:当
initialData更新时,自动执行打印逻辑
const Home = () => { const { initialData, setInitialData } = useInitialData(); useEffect(() => { axios.get('https://myUrl.com/my-endpoint') .then(res => setInitialData(res.data)) .catch(err => console.log(err)); }, []); // 监听initialData变化,打印最新值 useEffect(() => { console.log('最新InitialData:', initialData); }, [initialData]); // ... };
2. 解决无限循环请求问题
核心是让Context的value保持稳定引用,避免不必要的组件重新渲染:使用useMemo缓存value对象,只有当initialData变化时才重新创建:
// 修改InitialDataProvider组件 import React, { Dispatch, SetStateAction, createContext, useContext, useState, useMemo, // 引入useMemo } from 'react'; // ... 其他接口定义不变 const InitialDataProvider = ({ children }) => { const [initialData, setInitialData] = useState<InitialDataType | null>(null); // 用useMemo缓存context value,仅当initialData变化时更新 const contextValue = useMemo(() => ({ initialData, setInitialData }), [initialData]); // useState返回的setInitialData是稳定引用,无需加入依赖 return ( <Context.Provider value={contextValue}> {children} </Context.Provider> ); };
同时检查UkRegulation和GameIframe组件,确保它们没有重复发起相同请求的逻辑。
内容的提问来源于stack exchange,提问作者Rifat Cholakov
相关产品推荐
相关产品推荐

