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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 17:55:09