如何修复Redux Slice中的'WriteableDraft' TypeScript错误?
解决Redux切片TS7053索引类型错误的方案
错误核心原因
你遇到的TS7053错误,本质是TypeScript无法确认你用来索引对象的键是合法的——要么是索引值的类型被推断为any,要么是目标对象的类型定义不完整,导致TS无法验证索引的有效性。具体到你的代码,主要问题集中在:
- 缺失
Columns类型定义,state.columns的结构不明确 - Redux action的payload没有显式类型,导致索引值(比如
task.status、column)被判定为any initialState未绑定TaskSlice类型,TS推断的状态类型不够精确- 部分函数(比如
reduceTasks)的类型约束不严谨
分步修复方案
1. 补全所有缺失的类型定义
首先把Column、Columns以及各个action的payload类型补全,明确合法的列键范围:
// 定义单个列的类型 export type Column = { status: 'TO_DO' | 'IN_PROGRESS' | 'DONE' title: string taskIds: string[] } // 定义列集合的类型,键只能是指定的三个状态值 export type Columns = { [K in 'TO_DO' | 'IN_PROGRESS' | 'DONE']: Column } // 修正Task类型:原代码里实际用的是column而非board,这里同步修正 export type Task = { id: string title: string description?: string status: keyof Columns column: keyof Columns // 补上原代码中用到的column字段 } export type Tasks = { [key: string]: Task } export type TaskSlice = { boardId: string boardName: string boardDescription?: string tasks: Tasks columns: Columns columnOrder: ['TO_DO', 'IN_PROGRESS', 'DONE'] }
2. 给initialState绑定明确类型
让TS直接知道初始状态的结构,避免模糊推断:
export const initialState: TaskSlice = { boardId: '', boardName: '', boardDescription: '', tasks: {}, columns: { TO_DO: { status: 'TO_DO', title: 'To do', taskIds: [] }, IN_PROGRESS: { status: 'IN_PROGRESS', title: 'In progress', taskIds: [] }, DONE: { status: 'DONE', title: 'Done', taskIds: [] } }, columnOrder: ['TO_DO', 'IN_PROGRESS', 'DONE'] }
3. 为reducer的payload添加类型注解
每个reducer里明确payload的结构,让TS确认索引值是合法的列键:
比如addTask的payload:
addTask (state, { payload }: { payload: { task: Task } }) { // ... 原有逻辑 }
deleteTask的payload:
deleteTask (state, { payload }: { payload: { column: keyof Columns, id: string } }) { // ... 原有逻辑 }
4. 修正reduceTasks的类型问题
原函数参数用了元组[Task],实际应该是数组Task[],同时指定返回值类型:
export const reduceTasks = (tasks: Task[]): Tasks => { return tasks.reduce((acc, curr) => { return { ...acc, [curr.id]: curr } }, {} as Tasks) // 用as Tasks告诉TS初始累加器的类型 }
5. 处理moveTask中的索引安全
moveTask里的sourceColumn和destinationColumn要明确是keyof Columns,payload类型定义如下:
moveTask (state, { payload }: { payload: { taskId: string sourceIndex: number sourceColumn: keyof Columns destinationIndex: number destinationColumn: keyof Columns } }) { // ... 原有逻辑 }
完整修复后的代码
// 补全类型定义 export type Column = { status: 'TO_DO' | 'IN_PROGRESS' | 'DONE' title: string taskIds: string[] } export type Columns = { [K in 'TO_DO' | 'IN_PROGRESS' | 'DONE']: Column } export type Task = { id: string title: string description?: string status: keyof Columns column: keyof Columns } export type Tasks = { [key: string]: Task } export type TaskSlice = { boardId: string boardName: string boardDescription?: string tasks: Tasks columns: Columns columnOrder: ['TO_DO', 'IN_PROGRESS', 'DONE'] } // 修正reduceTasks函数 export const reduceTasks = (tasks: Task[]): Tasks => { return tasks.reduce((acc, curr) => { return { ...acc, [curr.id]: curr } }, {} as Tasks) } // 绑定initialState类型 export const initialState: TaskSlice = { boardId: '', boardName: '', boardDescription: '', tasks: {}, columns: { TO_DO: { status: 'TO_DO', title: 'To do', taskIds: [] }, IN_PROGRESS: { status: 'IN_PROGRESS', title: 'In progress', taskIds: [] }, DONE: { status: 'DONE', title: 'Done', taskIds: [] } }, columnOrder: ['TO_DO', 'IN_PROGRESS', 'DONE'] } const taskSlice = createSlice({ name: 'task', initialState, reducers: { addTask (state, { payload }: { payload: { task: Task } }) { const { task } = payload return { ...state, tasks: { ...state.tasks, [task.id]: task }, columns: { ...state.columns, [task.status]: { ...state.columns[task.status], taskIds: [ ...state.columns[task.status].taskIds, task.id ] } } } }, deleteTask (state, { payload }: { payload: { column: keyof Columns, id: string } }) { const { column, id } = payload const { [id]: deleted, ...restTasks } = state.tasks return { ...state, tasks: restTasks, columns: { ...state.columns, [column]: { ...state.columns[column], taskIds: state.columns[column].taskIds.filter(taskId => taskId !== id) } } } }, editTask (state, { payload }: { payload: { task: Task } }) { const { task } = payload return { ...state, tasks: { ...state.tasks, [task.id]: task }, columns: { ...state.columns, [task.column]: { ...state.columns[task.column], taskIds: [...state.columns[task.column].taskIds] } } } }, replaceTask (state, { payload }: { payload: { task: Task } }) { const { task } = payload return { ...state, tasks: { ...state.tasks, [task.id]: task } } }, moveTask (state, { payload }: { payload: { taskId: string sourceIndex: number sourceColumn: keyof Columns destinationIndex: number destinationColumn: keyof Columns } }) { const { taskId, sourceIndex, sourceColumn, destinationIndex, destinationColumn } = payload const sourceTaskIdsClone = [...state.columns[sourceColumn].taskIds] if (sourceColumn === destinationColumn) { sourceTaskIdsClone.splice(sourceIndex, 1) sourceTaskIdsClone.splice(destinationIndex, 0, taskId) return { ...state, columns: { ...state.columns, [sourceColumn]: { ...state.columns[sourceColumn], taskIds: sourceTaskIdsClone } } } } else { sourceTaskIdsClone.splice(sourceIndex, 1) const destinationTaskIdsClone = [...state.columns[destinationColumn].taskIds] destinationTaskIdsClone.splice(destinationIndex, 0, taskId) return { ...state, tasks: { ...state.tasks, [taskId]: { ...state.tasks[taskId], column: destinationColumn, status: initialState.columns[destinationColumn].status } }, columns: { ...state.columns, [sourceColumn]: { ...state.columns[sourceColumn], taskIds: sourceTaskIdsClone }, [destinationColumn]: { ...state.columns[destinationColumn], taskIds: destinationTaskIdsClone } } } } }, hydrateTasks (state, { payload }: { payload: { board: { id: string name: string description?: string TO_DO: string[] IN_PROGRESS: string[] DONE: string[] } tasks: Task[] } }) { const { board, tasks } = payload const { TO_DO, IN_PROGRESS, DONE } = board return { boardId: board.id, boardName: board.name, boardDescription: board.description, tasks: reduceTasks(tasks), columnOrder: ['TO_DO', 'IN_PROGRESS', 'DONE'], columns: { TO_DO: { status: 'TO_DO', title: 'To do', taskIds: [...TO_DO] }, IN_PROGRESS: { status: 'IN_PROGRESS', title: 'In progress', taskIds: [...IN_PROGRESS] }, DONE: { status: 'DONE', title: 'Done', taskIds: [...DONE] } } } } } }) export const { addTask, deleteTask, editTask, moveTask, hydrateTasks, replaceTask } = taskSlice.actions // 注:原代码里的addBoard、editBoard未在reducers中定义,这里移除避免报错 export default taskSlice.reducer
额外说明
- 原代码中
Task类型的board字段未被实际使用,反而用到了column,所以修正了Task类型 - 原代码导出的
addBoard、editBoard没有在reducers里定义,修复时移除了这两个导出,避免TS报错 - 所有索引操作现在都有明确的类型约束,TS能确认索引键的合法性,也就不会再抛出
TS7053错误
内容的提问来源于stack exchange,提问作者Neil Girardi
相关产品推荐
相关产品推荐

