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

TypeScript重构可拖拽滚动MUI Tabs遇react-beautiful-dnd报错求助

修复React Beautiful Dnd + MUI Tabs的"无法找到draggable"错误

错误原因

  1. 用数组索引作为draggableId和React元素key,拖拽后数组顺序改变,索引对应的元素发生变化,导致react-beautiful-dnd无法匹配到目标draggable元素。
  2. MUI的Tabs组件对直接子元素的结构有依赖,嵌套额外的div会干扰组件内部的DOM引用,引发识别问题。
  3. 克隆Tab元素时传递了冗余属性,导致事件处理逻辑冲突。

修复后的完整代码

import React from 'react';
import TabContext from "@mui/lab/TabContext";
import TabPanel from "@mui/lab/TabPanel";
import { DragDropContext, Droppable, DropResult, Draggable } from "react-beautiful-dnd";
import { Stack, styled, Tab, Tabs } from "@mui/material";

interface DraggableTabProps {
  label: string;
  value: string;
  index: number;
  draggableId: string;
  onChange: (event: React.SyntheticEvent, newValue: string) => void;
  currentValue: string;
};

const DraggableTab = (props: DraggableTabProps) => {
  return (
    <Draggable
      draggableId={props.draggableId}
      index={props.index}
      disableInteractiveElementBlocking
    >
      {(draggableProvided) => (
        <Tab
          ref={draggableProvided.innerRef}
          {...draggableProvided.draggableProps}
          {...draggableProvided.dragHandleProps}
          label={props.label}
          value={props.value}
          onChange={props.onChange}
          selected={props.currentValue === props.value}
        />
      )}
    </Draggable>
  );
}

const StyledTabs = styled(Tabs)();

export default function DraggableTabComponent() {
  const [value, setValue] = React.useState('1');
  
  const handleChange = (event: React.SyntheticEvent, newValue: string) => {
    setValue(newValue);
  };

  const [tabs, setTabs] = React.useState(
    [...Array(25)].map((_, index) => ({
      id: `tab${index + 1}`,
      label: `Tab ${index + 1}`,
      value: `${index + 1}`,
      content: `Content ${index + 1}`
    }))
  );

  const onDragEnd = (result: DropResult) => {
    if (!result.destination) return;
    const newTabs = Array.from(tabs);
    const draggedTab = newTabs.splice(result.source.index, 1)[0];
    newTabs.splice(result.destination.index, 0, draggedTab);
    setTabs(newTabs);
    // 拖拽后保持选中当前标签的内容
    setValue(draggedTab.value);
  };

  return (
    <TabContext value={value}>
      <Stack>
        <DragDropContext onDragEnd={onDragEnd}>
          <Droppable droppableId="tabs" direction="horizontal">
            {(droppableProvided) => (
              <StyledTabs
                ref={droppableProvided.innerRef}
                {...droppableProvided.droppableProps}
                value={value}
                variant="scrollable"
                scrollButtons
                allowScrollButtonsMobile
                sx={{ minHeight: 48 }} // 保证拖拽区域高度稳定
              >
                {tabs.map((tab, index) => (
                  <DraggableTab
                    key={tab.id}
                    draggableId={tab.id}
                    label={tab.label}
                    value={tab.value}
                    index={index}
                    onChange={handleChange}
                    currentValue={value}
                  />
                ))}
                {droppableProvided.placeholder}
              </StyledTabs>
            )}
          </Droppable>
        </DragDropContext>
      </Stack>
      {tabs.map((tab) => (
        <TabPanel value={tab.value} key={tab.id}>
          {tab.content}
        </TabPanel>
      ))}
    </TabContext>
  );
}

关键修复点

  1. 使用稳定的唯一标识:用每个tab的id作为draggableId和React元素的key,拖拽后元素的标识不会随索引变化而改变,避免react-beautiful-dnd找不到目标元素。
  2. 简化Draggable结构:直接在Draggable内部渲染Tab组件,不再克隆元素,避免属性冲突和DOM结构干扰。
  3. 完善拖拽结束逻辑:增加!result.destination的判断,处理拖拽未完成的情况;拖拽后自动选中被拖拽的标签,保持内容同步。
  4. 稳定Tabs高度:添加sx={{ minHeight: 48 }}保证Tabs区域高度稳定,避免拖拽时布局跳动影响识别。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 08:01:26