ReactTS中useReducer报错:state.todos不可迭代问题求助
问题分析
- useReducer初始状态与类型不匹配:你定义的
State是包含todos字段的对象,但初始化useReducer时传入的是数组todoList,导致state本身就是数组而非预期的对象,因此state.todos为undefined,无法进行展开迭代。 - 状态未同步到Context:Button组件内的useReducer仅维护组件内部状态,没有更新Context中的
todoList,即便修复第一个问题,其他组件(如Todos)也无法获取新增的待办项。 - Context类型不严谨:
todoList用object[]类型会丢失类型检查,应直接使用TodoProps[]。
修复步骤
1. 修正LayoutContext.tsx的类型与初始值
import { createContext, useState, ReactNode } from 'react' import { TodoProps } from '../Components/TodoComponent/Todo'; // 明确Context类型,避免any导致的类型丢失 export const TodoListContext = createContext<{ todoList: TodoProps[], setTodoList: React.Dispatch<React.SetStateAction<TodoProps[]>> }>({ todoList: [], setTodoList: () => {} }) export const TitleContext = createContext<{ title: string, setTitle: React.Dispatch<React.SetStateAction<string>> }>({ title: '', setTitle: () => {} }) type LayoutContextProps = { children: ReactNode } export const LayoutContext = ({ children }: LayoutContextProps): JSX.Element => { const [title, setTitle] = useState<string>('') // 把object[]改为TodoProps[],并给初始项补充required的completed字段 const [todoList, setTodoList] = useState<TodoProps[]>([ {title: 'Todo 1', id: 418759, completed: false}, {title: 'Todo 2', id: 440123, completed: false} ]) return ( <TitleContext.Provider value={{ title, setTitle }}> <TodoListContext.Provider value={{ todoList, setTodoList }}> {children} </TodoListContext.Provider> </TitleContext.Provider> ) }
2. 修正Button.tsx的useReducer使用与状态同步
import styles from './Button.module.scss' import { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode, useReducer, useContext } from 'react'; import { TodoListContext, TitleContext } from '../../Context/LayoutContext'; import { TodoProps } from 'Components/TodoComponent/Todo'; interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> { children: ReactNode } export const Button = ({ children }: ButtonProps): JSX.Element => { const { todoList, setTodoList } = useContext(TodoListContext) const { title, setTitle } = useContext(TitleContext) type State = { todos: TodoProps[] } type Action = { type: 'addTodo', payload: string } const reducer = (state: State, action: Action) => { switch (action.type) { case 'addTodo': const newTodoCard: TodoProps = { title: action.payload, id: Math.floor(Math.random() * 100000), // 增大随机范围避免ID重复 completed: false } const newTodos = [...state.todos, newTodoCard] setTodoList(newTodos) // 同步到Context状态 return { todos: newTodos } default: return state // 必须处理default分支,防止状态异常 } } // 传入符合State类型的初始对象,而非直接传数组 const [state, dispatch] = useReducer(reducer, { todos: todoList }) const handleAdd = (e: React.FormEvent) => { e.preventDefault() if (!title.trim()) return // 过滤空输入 dispatch({ type: 'addTodo', payload: title}) setTitle('') } return ( <button type='submit' onClick={handleAdd} className={styles.button} >{children}</button> ) }
可选优化:统一在Context中使用useReducer
如果希望状态管理更集中,可将useReducer移到LayoutContext中,避免组件内部维护独立状态:
// LayoutContext.tsx import { createContext, useReducer, ReactNode } from 'react' import { TodoProps } from '../Components/TodoComponent/Todo'; type Action = { type: 'addTodo', payload: string } const todoReducer = (state: TodoProps[], action: Action) => { switch(action.type) { case 'addTodo': return [...state, { title: action.payload, id: Math.floor(Math.random() * 100000), completed: false }] default: return state } } export const TodoListContext = createContext<{ todos: TodoProps[], dispatch: React.Dispatch<Action> }>({ todos: [], dispatch: () => {} }) export const TitleContext = createContext<{ title: string, setTitle: React.Dispatch<React.SetStateAction<string>> }>({ title: '', setTitle: () => {} }) type LayoutContextProps = { children: ReactNode } export const LayoutContext = ({ children }: LayoutContextProps): JSX.Element => { const [title, setTitle] = useState<string>('') const [todos, dispatch] = useReducer(todoReducer, [ {title: 'Todo 1', id: 418759, completed: false}, {title: 'Todo 2', id: 440123, completed: false} ]) return ( <TitleContext.Provider value={{ title, setTitle }}> <TodoListContext.Provider value={{ todos, dispatch }}> {children} </TodoListContext.Provider> </TitleContext.Provider> ) }
简化后的Button组件:
// Button.tsx import styles from './Button.module.scss' import { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode, useContext } from 'react'; import { TodoListContext, TitleContext } from '../../Context/LayoutContext'; interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> { children: ReactNode } export const Button = ({ children }: ButtonProps): JSX.Element => { const { dispatch } = useContext(TodoListContext) const { title, setTitle } = useContext(TitleContext) const handleAdd = (e: React.FormEvent) => { e.preventDefault() if (!title.trim()) return dispatch({ type: 'addTodo', payload: title}) setTitle('') } return ( <button type='submit' onClick={handleAdd} className={styles.button} >{children}</button> ) }
内容的提问来源于stack exchange,提问作者Lvasche
相关产品推荐
相关产品推荐

