React DnD-Kit拖拽时元素不可见问题排查
React DnD-Kit拖拽异常原因分析
我用React DnD-Kit实现拖拽功能,参考官方Sortable示例修改。最初用useSortable({id: customer.id})时,拖拽有动画但释放后元素位置重置;改成useSortable({id: customer})后,释放能更新列表,但拖拽过程中元素不可见。请问这一现象的原因是什么?
完整代码
SortableCustomerRecord组件
import React from 'react'; import {useSortable} from '@dnd-kit/sortable'; import {CSS} from '@dnd-kit/utilities'; export function SortableDriverRecord({ customer }) { const { attributes, listeners, setNodeRef, transform, transition, } = useSortable({id: customer}); const style = { transform: CSS.Transform.toString(transform), transition, }; return ( <div ref={setNodeRef} style={style} {...attributes} {...listeners}> <p>{customer.name}</p> </div> ); }
RoundCustomerList组件
import axios from "axios"; //React import React, { useRef, useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; //Icons import { faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; //Gloabal auth via user import { useAuthContext } from "../../../Hooks/useAuthContext"; //Components import Spinner from "../../../Components/Loading/Spinner"; import {SortableCustomerRecord} from "./SortableCustomerRecord"; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, } from '@dnd-kit/core'; import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, } from '@dnd-kit/sortable'; export default function RoundCustomerList({ selectedRound, roundCustomers, setRoundCustomers, setSelectedCustomer, customersLoading, }) { console.log(roundCustomers) const [items, setItems] = useState(roundCustomers); const { user } = useAuthContext(); const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); function handleDragEnd(event) { const {active, over} = event; if (active.id !== over.id) { setItems((items) => { const oldIndex = items.indexOf(active.id); const newIndex = items.indexOf(over.id); return arrayMove(items, oldIndex, newIndex); }); } } useEffect(() => { console.log("list changed") const data = { roundCustomers: [], }; items.forEach(function(customer, index){ data.roundCustomers.push({ customerId: customer.customerId, roundId: selectedRound, position: index, }); }); axios .post( `https://${import.meta.env.VITE_SERVER_URL}/api/round/${selectedRound}/customers/`, data, { headers: { Authorization: `bearer ${user.token}` }, } ) }, [items]); return ( <div className="mb-3" style={{ height: "100%" }}> <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd} > <SortableContext items={items} strategy={verticalListSortingStrategy} > {items.map(customer => <SortableCustomerRecord key={customer.id} customer={customer} />)} </SortableContext> </DndContext> </div> )
原因分析及解决方案
1. 用customer.id时位置重置的原因
你在handleDragEnd里用items.indexOf(active.id)查找索引,但items数组存的是完整的customer对象,而active.id是字符串/数字类型的id值。indexOf无法在对象数组中匹配到基础类型值,导致oldIndex和newIndex都是-1,arrayMove不会修改原数组,最终释放后列表位置重置。
2. 用customer对象时拖拽元素不可见的原因
DnD-Kit要求拖拽id必须是稳定且可序列化的基础类型(字符串、数字),直接传对象作为id会触发两个问题:
- 内部状态对比依赖对象引用,组件重渲染后
transform等拖拽状态无法正确关联到当前元素,导致样式失效,看起来像是“不可见”; - 虽然
indexOf通过对象引用相等能找到索引,但这不符合DnD-Kit的设计规范,后续容易出现其他异常。
正确解决方式
- 保持
useSortable的id为customer.id(唯一稳定的基础类型值); - 修改
handleDragEnd的索引查找逻辑,用findIndex根据id匹配对象:
function handleDragEnd(event) { const {active, over} = event; if (active.id !== over.id) { setItems((items) => { const oldIndex = items.findIndex(item => item.id === active.id); const newIndex = items.findIndex(item => item.id === over.id); return arrayMove(items, oldIndex, newIndex); }); } }
- 可选优化:将
SortableContext的items改为id数组,更符合DnD-Kit的设计:
<SortableContext items={items.map(item => item.id)} strategy={verticalListSortingStrategy} > {items.map(customer => <SortableCustomerRecord key={customer.id} customer={customer} />)} </SortableContext>
修改后即可实现拖拽动画正常、释放后位置正确更新的效果。
内容的提问来源于stack exchange,提问作者Gavin Coates
相关产品推荐
相关产品推荐

