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

ReactTS中useReducer报错:state.todos不可迭代问题求助

问题分析

  1. useReducer初始状态与类型不匹配:你定义的State是包含todos字段的对象,但初始化useReducer时传入的是数组todoList,导致state本身就是数组而非预期的对象,因此state.todos为undefined,无法进行展开迭代。
  2. 状态未同步到Context:Button组件内的useReducer仅维护组件内部状态,没有更新Context中的todoList,即便修复第一个问题,其他组件(如Todos)也无法获取新增的待办项。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 06:13:12