React useContext报对象不可迭代Symbol.iterator读取错误
问题原因
报错是三个问题叠加导致的:
- 你写的
defaultContext函数每次传null调用时,都会执行createContext({})创建一个全新的Context对象。父组件里Provider用的Context、子组件里调用useContext拿的Context是完全独立的两个实例,子组件根本读不到Provider注入的值,只能拿到Context初始化的空对象{}。 - 你往Context里塞的value是对象格式
{ state, dispatch },子组件却用数组写法const [state, dispatch] = useContext(...)解构,空对象本身没有迭代器,执行数组解构直接触发「object is not iterable」报错。 - 你用
useMemo包裹Provider组件时传了空依赖数组,后续state更新时Provider不会重渲染,就算Context实例对了,子组件也拿不到最新的状态值。
修复方案
直接改通用Provider的实现,把默认Context全局唯一化,同时封装配套的读取hook,避免手动传Context出错:
1. 重写useGenericReducer.tsx
import React, { useReducer, createContext, useContext } from 'react'; // 全局默认Context,仅初始化一次,保证所有调用方拿到的是同一个实例 const globalDefaultContext = createContext<{ state: any; dispatch: React.Dispatch<any>; } | null>(null); export function useGenericProvider<State, Action>( reducer: React.Reducer<State, Action>, initialState: State, customContext?: React.Context<any> ) { const activeContext = customContext ?? globalDefaultContext; const [state, dispatch] = useReducer(reducer, initialState); // 不要用useMemo缓存组件,直接返回Provider组件,保证state变化时能正常重渲染 const Provider = ({ children }: { children: React.ReactNode }) => { return ( <activeContext.Provider value={{ state, dispatch }}> {children} </activeContext.Provider> ); }; // 封装配套的读取hook,避免用户手动找Context实例传错 const useProviderContext = () => { const ctxValue = useContext(activeContext); if (!ctxValue) { throw new Error('useProviderContext must be used inside the corresponding Provider'); } return ctxValue; }; return { Provider, useProviderContext, context: activeContext }; }
2. 修改Child.tsx
直接用hook返回的useProviderContext读取值,不要自己调用defaultContext创建新实例,同时用对象格式解构:
import React from 'react'; import { useGenericProvider } from './useGenericReducer'; // 复用全局默认Context对应的读取hook,自定义Context场景可以自己传Context给useGenericProvider生成对应hook const { useProviderContext } = useGenericProvider( (state: {count: number}, action: any) => state, { count: 0 } ); const Child = () => { const { state, dispatch } = useProviderContext(); return ( <div> <div>当前计数:{state.count}</div> <button onClick={() => dispatch('ADD')}>加1</button> <button onClick={() => dispatch('SUB')}>减1</button> </div> ); }; export default Child;
3. Parent.tsx基本无需改动
正常调用useGenericProvider拿到Provider包裹子组件即可,需要用自定义Context时把自己创建的Context作为第三个参数传入就行:
import React from 'react'; import { render } from 'react-dom'; import { useGenericProvider } from './useGenericReducer'; import Child from './Child'; interface IInitState { count: number; } const App: React.FC = () => { const initState: IInitState = { count: 0 }; function reducer(state: IInitState, action: any) { switch (action) { case 'ADD': return { ...state, count: state.count + 1 }; case 'SUB': return { ...state, count: state.count - 1 }; default: return state; } } const { Provider } = useGenericProvider(reducer, initState); return ( <Provider> <Child /> </Provider> ); }; render(<App />, document.getElementById('root'));
修复点说明
- 默认Context全局只初始化一次,从根源避免多实例导致读不到值的问题。
- 去掉了错误的空依赖useMemo缓存Provider的写法,保证状态更新时组件正常重渲染。
- 封装了配套的读取hook,统一解构格式,避免手动调用useContext时传错实例、解构格式不对的问题。
- 保留自定义Context传参能力,用户需要隔离Context场景时可以传入自己创建的Context实例。
内容的提问来源于stack exchange,提问作者BARNOWL
相关产品推荐
相关产品推荐

