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

React+Redux Toolkit TodoList删除项后报todos.map非函数错误求助

问题修复:删除待办项后出现TypeError: todos.map is not a function

错误根源

  1. 异步Thunk类型重复:removeTodosThunk的action type写成了'todo/fetchTodos',和获取待办列表的fetchTodos完全一致。这导致删除操作完成后,会触发fetchTodos.fulfilled的reducer逻辑,把删除接口返回的单个Todo对象(而非数组)赋值给state.todos,后续map调用自然报错。
  2. Thunk返回类型错误:removeTodosThunk定义的返回类型是Todo[],但DELETE接口通常返回的是被删除的单个Todo对象,类型不匹配。
  3. 缺少删除操作的Reducer处理:没有为removeTodosThunk添加对应的extraReducers逻辑,反而错误复用了获取列表的逻辑。

修复步骤

1. 修正removeTodosThunk的定义

修改todoSlice.ts中的删除异步Thunk:

// 修正type名称和返回类型
export const removeTodosThunk = createAsyncThunk<Todo, number>(
    'todo/removeTodo', // 改为唯一的type标识
    async function(id) {
        const {data} = await axios.delete(`http://localhost:3004/todos/${id}`);
        return data; // 返回单个被删除的Todo对象
    }
)

2. 添加删除操作的Reducer处理

在todoSlice的extraReducers中新增removeTodosThunk的处理逻辑:

extraReducers: (builder) => {
    builder.addCase(fetchTodos.pending, (state) => {
        state.status = 'loading';
        state.error = null;
    })
    .addCase(fetchTodos.fulfilled, (state, action) => {
        state.status = 'resolved';
        state.todos = action.payload;
    })
    .addCase(addTodosThunk.fulfilled, (state, { payload } : { payload: Todo }) => {
        console.log(payload);
        state.todos.push(payload);
    })
    .addCase(fetchTodos.rejected, (state, action) => {
        state.status = 'rejected';
    })
    // 新增删除完成的处理逻辑
    .addCase(removeTodosThunk.fulfilled, (state, action) => {
        state.todos = state.todos.filter(todo => todo.id !== action.payload.id);
        state.status = 'resolved';
    })
    .addCase(removeTodosThunk.rejected, (state) => {
        state.status = 'rejected';
        state.error = '删除失败';
    });
};

3. 统一RootState类型

修改TodoList.tsx中的RootState引用,使用store导出的类型而非自定义:

import { useSelector } from 'react-redux';
import TodoItem from './TodoItem';
import React from 'react';
// 导入store中导出的RootState
import type { RootState } from '../store';
// 导入todoSlice中的Todo类型
import type { Todo } from '../store/todoSlice';

const TodoList: React.FC = () => {
    const todos = useSelector((state: RootState) => state.todos.todos);

    return (
        <ul>
            {todos.map((todo) => (
                <TodoItem defaultTitle={''} key={todo.id} {...todo} />
            ))}
        </ul>
    );
};

export default TodoList;

4. 优化TodoItem的Dispatch使用

使用store导出的useAppDispatch替代手动定义Dispatch,获得类型安全:

import { toggleComplete, removeTodosThunk } from '../store/todoSlice';
import React from 'react';
// 导入useAppDispatch
import { useAppDispatch } from '../store';

interface TodoItemProps {
  defaultTitle: string;
  id: number;
  title: string;
  completed: boolean;
}

const TodoItem: React.FC<TodoItemProps> = ({ id, title, completed }) => {
  const dispatch = useAppDispatch(); // 使用类型化的dispatch

  const handleToggleComplete = (): void => {
    dispatch(toggleComplete({id})); // 无需any断言
  };

  const handleRemoveTodo = (): void => {
    dispatch(removeTodosThunk(id)); // 无需any断言
  };

  return (
    <li>
      <input
        type='checkbox'
        checked={completed}
        onChange={handleToggleComplete}
      />
      <span>{title}</span>
      <button onClick={handleRemoveTodo}>&times;</button>
    </li>
  );
};

export default TodoItem;

额外说明

如果你的DELETE接口返回空值而非被删除的Todo对象,可以修改removeTodosThunk的返回类型为void,并在fulfilled处理中直接根据传入的id过滤:

export const removeTodosThunk = createAsyncThunk<void, number>(
    'todo/removeTodo',
    async function(id) {
        await axios.delete(`http://localhost:3004/todos/${id}`);
        // 无需返回数据
    }
)

// 对应的reducer处理
.addCase(removeTodosThunk.fulfilled, (state, action) => {
    // action.meta.arg就是传入的id
    state.todos = state.todos.filter(todo => todo.id !== action.meta.arg);
    state.status = 'resolved';
})

内容的提问来源于stack exchange,提问作者красюк виктор

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 23:05:04