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

如何修复Zustand中reorderTodo函数的TypeScript编译错误?

修复Zustand + Dnd Kit拖拽排序的TypeScript类型错误

核心问题分析

构建失败的本质是TypeScript严格模式下,拖拽排序相关代码存在类型不匹配或类型推断缺失问题——开发模式可能因tsconfig宽松配置跳过检查,但生产构建会严格校验。

分步修复方案

1. 明确类型定义(基础)

先确保Todo实体和Zustand Store的类型完全明确,避免隐式类型推断导致的错误:

// 定义Todo实体类型
interface Todo {
  id: string; // 统一用string类型,和Dnd Kit的id类型对齐
  text: string;
  completed: boolean;
}

// 定义Store接口,约束所有方法和状态的类型
interface TodoStore {
  todos: Todo[];
  addTodo: (text: string) => void;
  deleteTodo: (id: string) => void;
  toggleTodo: (id: string) => void;
  reorderTodo: (startIndex: number, endIndex: number) => void;
}

2. 编写类型安全的reorderTodo函数

使用Dnd Kit官方提供的arrayMove工具函数处理数组重排,它已内置类型安全处理,避免手动操作数组(slice/splice)时的类型错误:

import { create } from 'zustand';
import { arrayMove } from '@dnd-kit/sortable'; // 导入Dnd Kit的数组工具函数

const useTodoStore = create<TodoStore>((set) => ({
  todos: [],
  addTodo: (text) => set((state) => ({
    todos: [...state.todos, { id: crypto.randomUUID(), text, completed: false }]
  })),
  deleteTodo: (id) => set((state) => ({
    todos: state.todos.filter(todo => todo.id !== id)
  })),
  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map(todo => 
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    )
  })),
  // 类型安全的重排函数,参数明确为number类型
  reorderTodo: (startIndex, endIndex) => set((state) => ({
    todos: arrayMove(state.todos, startIndex, endIndex)
  })),
}));

3. 拖拽事件处理的类型校验

在父组件处理onDragEnd事件时,必须确保从拖拽结果中获取的索引是有效的(非-1),避免传入无效值触发类型错误:

import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { useTodoStore } from './store';

const TodoList = () => {
  const todos = useTodoStore(state => state.todos);
  const reorderTodo = useTodoStore(state => state.reorderTodo);

  const sensors = useSensors(
    useSensor(PointerSensor),
    useSensor(KeyboardSensor),
  );

  const handleDragEnd = (event: any) => {
    const { active, over } = event;

    // 排除拖拽到自身的情况
    if (active.id !== over.id) {
      // 根据id查找对应索引,明确类型为number
      const startIndex = todos.findIndex(todo => todo.id === active.id);
      const endIndex = todos.findIndex(todo => todo.id === over.id);

      // 确保索引有效,避免传入-1
      if (startIndex !== -1 && endIndex !== -1) {
        reorderTodo(startIndex, endIndex);
      }
    }
  };

  return (
    <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
      <SortableContext 
        items={todos.map(todo => todo.id)} 
        strategy={verticalListSortingStrategy}
      >
        {todos.map(todo => (
          <TodoItem key={todo.id} todo={todo} />
        ))}
      </SortableContext>
    </DndContext>
  );
};

4. 待办项组件的类型对齐

确保TodoItem组件中useSortable的id参数和Todo的id类型完全一致:

import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Todo } from './store';

const TodoItem = ({ todo }: { todo: Todo }) => {
  const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
    id: todo.id, // 直接传入Todo的id,类型完全匹配
  });

  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
  };

  return (
    <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
      <input 
        type="checkbox" 
        checked={todo.completed} 
        onChange={() => useTodoStore.getState().toggleTodo(todo.id)}
      />
      <span>{todo.text}</span>
      <button onClick={() => useTodoStore.getState().deleteTodo(todo.id)}>删除</button>
    </div>
  );
};

常见错误点排查

  • id类型不匹配:如果Todo的id是number类型,而Dnd Kit默认使用string类型的id,会导致类型推断错误,统一为string即可解决。
  • 未处理无效索引:findIndex可能返回-1,若直接传入reorder函数会触发类型错误,必须添加索引有效性判断。
  • 手动数组操作的类型问题:手动用slice/splice重排数组时,容易出现数组元素类型推断错误,使用arrayMove工具函数可避免此问题。

内容的提问来源于stack exchange,提问作者kilex

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 14:43:11