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

如何修复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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 09:50:27