Dnd-kit嵌套拖拽功能正常,但外层拖拽无动画问题求助
两层嵌套Dnd-kit拖拽外层无动画问题
用二维数组实现两层嵌套Dnd-kit拖拽功能时,内层拖拽完全正常,但外层拖拽仅能正常排序元素,拖拽移动及排序过程中无任何动画效果。
问题原因分析
- 外层
SortableContext传入的items是完整对象数组,而非useSortable对应的id数组,导致排序策略无法正确识别元素 - 外层
RubriqueAdmin组件中useSortable配置了animateLayoutChanges: () => false,直接禁用了布局动画 - 部分组件使用数组索引作为
key,排序后易引发渲染异常,间接影响动画效果
修复方案及代码修改
1. 修正外层AdminCartePage的SortableContext
将items改为id数组,同时把组件key从索引改为元素唯一id:
<SortableContext items={newCarte.map(r => r.id)} strategy={rectSortingStrategy}> { newCarte.map((rubrique: any) => { return ( <RubriqueAdmin key={rubrique.id} newCarte={newCarte} setNewCarte={setNewCarte} rubrique={rubrique} /> ) })} </SortableContext>
2. 修改RubriqueAdmin的useSortable配置
移除animateLayoutChanges: () => false,启用默认布局动画:
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({id: rubrique.id})
3. 统一内层组件的key配置
将ArticleAdmin的key从索引改为article.id,避免渲染异常:
<ArticleAdmin key={article.id} newCarte={newCarte} setNewCarte={setNewCarte} rubrique={rubrique} article={article} />
完整修正后的关键代码片段
AdminCartePage.tsx
import { SortableContext, arrayMove, rectSortingStrategy } from "@dnd-kit/sortable" import { DndContext, closestCenter } from "@dnd-kit/core" import CreateRubriqueAdmin from "./CreateRubriqueAdmin"; import RubriqueAdmin from "./RubriqueAdmin"; import { useState } from "react"; interface AdminCartePageProps { Carte: any } export default function AdminCartePage({ Carte }: AdminCartePageProps) { const [ newCarte, setNewCarte ] = useState(Carte) function onDragEnd(event: any) { const { active, over } = event if (active.id === over.id) return ; setNewCarte((newCarte: any) => { const oldIndex = newCarte.findIndex((rubrique: any) => rubrique.id === active.id) const newIndex = newCarte.findIndex((rubrique: any) => rubrique.id === over.id) return arrayMove(newCarte, oldIndex, newIndex) }) } return ( <div> <div> <DndContext collisionDetection={closestCenter} onDragEnd={onDragEnd}> <SortableContext items={newCarte.map(r => r.id)} strategy={rectSortingStrategy}> { newCarte.map((rubrique: any) => { return ( <RubriqueAdmin key={rubrique.id} newCarte={newCarte} setNewCarte={setNewCarte} rubrique={rubrique} /> ) })} </SortableContext> </DndContext> </div> </div> ) }
RubriqueAdmin.tsx
'use client' import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable" import { DndContext, closestCenter } from "@dnd-kit/core" import ArticleAdmin from "./ArticleAdmin"; import { CSS } from "@dnd-kit/utilities"; interface RubriqueAdminProps { newCarte: any, setNewCarte: React.SetStateAction<any>, rubrique: any, } export default function RubriqueAdmin({ newCarte, setNewCarte, rubrique }: RubriqueAdminProps) { const { attributes, listeners, setNodeRef, transform, transition } = useSortable({id: rubrique.id}) const style = { transition, transform: CSS.Translate.toString(transform) } function onDragEnd(event: any) { const { active, over } = event if (active.id === over.id) return ; let tmpCarte = [...newCarte] const rubriqueIndex = newCarte.findIndex((rub: any) => rub.id === rubrique.id) const oldIndex = rubrique.articles.findIndex((article: any) => article.id === active.id) const newIndex = rubrique.articles.findIndex((article: any) => article.id === over.id) tmpCarte[rubriqueIndex].articles = arrayMove(newCarte[rubriqueIndex].articles, oldIndex, newIndex) setNewCarte(tmpCarte) } return ( <div ref={setNodeRef} {...attributes} {...listeners} style={style}> <h1>{rubrique.type}</h1> <DndContext collisionDetection={closestCenter} onDragEnd={onDragEnd}> <SortableContext items={rubrique.articles.map(a => a.id)} strategy={verticalListSortingStrategy}> { rubrique.articles.map((article: any) => { return ( <ArticleAdmin key={article.id} newCarte={newCarte} setNewCarte={setNewCarte} rubrique={rubrique} article={article} /> ) })} </SortableContext> </DndContext> </div> ) }
ArticleAdmin.tsx
'use client' import { useSortable } from "@dnd-kit/sortable" import { CSS } from "@dnd-kit/utilities"; interface ArticleAdminProps { newCarte: any, setNewCarte: React.SetStateAction<any>, rubrique: any, article: any } export default function ArticleAdmin({ newCarte, setNewCarte, rubrique, article }: ArticleAdminProps) { const { attributes, listeners, setNodeRef, transform, transition } = useSortable({id: article.id}) const style = { transition, transform: CSS.Translate.toString(transform) } return ( <div ref={setNodeRef} {...attributes} {...listeners} style={style}> <p>{article.Titre}</p> <p>{article.Prix + ' €'}</p> </div> ) }
验证要点
- 外层拖拽时,元素移动过程中应有平滑的跟随动画
- 排序完成后,其他元素应有布局过渡动画
- 内层拖拽功能保持正常
内容的提问来源于stack exchange,提问作者CanardWcs
相关产品推荐
相关产品推荐

