如何修复TypeScript React中“void[]无法赋值给SetStateAction<ITask[]>”错误
React TypeScript Todo应用:点击Complete按钮页面不更新的问题修复
问题描述
用TypeScript开发Todo列表应用时,点击任务的「Complete」按钮后,控制台显示数据已更改,但页面没有更新,同时出现TypeScript错误:
TS2345: Argument of type 'void[]' is not assignable to parameter of type 'SetStateAction<ITask[]>'. Type 'void[]' is not assignable to type 'ITask[]'. Type 'void' is not assignable to type 'ITask'.
错误原因
- map函数无返回值:
completeTask中使用map时未返回每个任务对象,导致生成void[]数组,不符合ITask[]类型要求,触发TS报错。 - 直接修改原状态对象:直接执行
task.completed = true属于篡改原状态引用,React无法检测到状态变化,因此不会触发组件重渲染。 - 使用数组索引作为任务ID:传递给
TodoItem的id用的是map的索引值,而非任务自身的id,列表增删时会导致索引与实际任务不匹配。
修复步骤
1. 修正completeTask函数,生成新状态对象
使用map遍历任务列表时,对目标任务返回新对象(避免修改原状态),其他任务直接返回;同时用函数式更新setTodos,确保获取最新状态:
const completeTask = (id: number) => { setTodos(prevTodos => prevTodos.map(task => task.id === id ? {...task, completed: true} : task ) ); }
2. 传递任务自身ID替代索引
渲染TodoItem时,使用task.id作为id属性和completeTask的参数,同时将task.id设为React列表的key:
{todos.map((task) => ( <TodoItem key={task.id} id={task.id} todo={task.todo} completed={task.completed} completeItem={() => completeTask(task.id)} startDate={task.startDate} endDate={task.endDate ?? ""} /> ))}
3. 优化TodoItem视觉反馈(可选)
原代码中completed为true时仅添加透明边框,视觉无变化,可改为更直观的样式:
<div className={`bg-white rounded-[10px] flex flex-col w-[300px] p-[18px] h-[270px] justify-between m-10 shadow-[2px_4px_20px_0px_rgba(0,0,0,0.04)] ${completed ? 'bg-gray-100 line-through' : ''}`}>
完整修正代码
App.tsx
import React, { FC, useState } from 'react'; import { TodoItem } from './components/TodoItem'; interface ITask { id: number; todo: string; startDate: string; endDate?: string; completed: boolean; } const App: FC = () => { const [todos, setTodos] = useState<ITask[]>([ { id: 0, todo: "Buy Bread", startDate: "12/12/2023", endDate: "12/14/2023", completed: false }, { id: 1, todo: "Buy House", startDate: "12/12/2023", endDate: "12/13/2023", completed: false } ]); const completeTask = (id: number) => { setTodos(prevTodos => prevTodos.map(task => task.id === id ? {...task, completed: true} : task ) ); } return ( <> {todos.map((task) => ( <TodoItem key={task.id} id={task.id} todo={task.todo} completed={task.completed} completeItem={() => completeTask(task.id)} startDate={task.startDate} endDate={task.endDate ?? ""} /> ))} </> ); } export default App;
TodoItem.tsx
import React, { FC } from 'react'; export interface ITodoItemProps { id: number; todo: string; startDate: string; endDate?: string; completed: boolean; completeItem: (id: number) => void; } export const TodoItem: FC<ITodoItemProps> = ({ id, todo, startDate, endDate, completeItem, completed }) => { return ( <div className={`bg-white rounded-[10px] flex flex-col w-[300px] p-[18px] h-[270px] justify-between m-10 shadow-[2px_4px_20px_0px_rgba(0,0,0,0.04)] ${completed ? 'bg-gray-100 line-through' : ''}`}> <div> <div className='flex flex-row gap-3 mb-4 align-middle'> <img src="images/calendar.svg" className='w-[20px]' alt="calendar" /> <p className='pb-0 mb-0 font-mono text-[12px] align-middle text-third mt-1'>{startDate}</p> <img src="images/arrow.svg" alt="arrow" /> <p className='pb-0 mb-0 font-mono text-[12px] align-middle text-third mt-1'>{endDate}</p> </div> <p className='text-[17px] leading-[25px] max-h-[150px] overflow-y-auto'>{todo}</p> </div> <div className='w-full py-[8px] px-[12px] bg-second rounded-[10px] flex flex-row justify-between'> <img src="images/edit.svg" alt="edit" className='hover:cursor-pointer' /> <div className='flex flex-row'> <button className='text-third px-[12px]'>Delete</button> <button onClick={() => completeItem(id)} className="bg-white px-[12px] py-[4px] rounded-[10px]">Complete</button> </div> </div> </div> ); }
内容的提问来源于stack exchange,提问作者Caleb Gross
相关产品推荐
相关产品推荐

