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

react-beautiful-dnd嵌套Material-UI表格div破坏布局解决方案

问题根因

HTML表格对DOM层级有强校验,只允许table > thead/tbody > tr > td的固定嵌套关系,中间插入div这类非表格标签时,浏览器会自动重构DOM结构,直接打乱Material-UI表格的原生布局。而React.Fragment是虚拟占位节点,不接收真实DOM ref,无法直接给Droppable绑定所需的innerRef。

可行解决方案

按推荐优先级排序:

方案1:使用合法表格标签作为Droppable容器(最稳定无兼容问题)

HTML规范原生支持一个table下存在多个<tbody>标签,直接把嵌套Droppable的容器从div替换为Material-UI自带的TableBody组件即可,完全符合DOM规范,不需要额外改样式。
修改RatingTemplateBlockRow中的嵌套Droppable代码如下:

<Droppable droppableId={`droppableVM-${block.id}`}>
    {(provided) => {
        return (
            <React.Fragment>
                {/* 替换原div为TableBody,合法表格元素可正常绑定ref */}
                <TableBody 
                    ref={provided.innerRef} 
                    {...provided.droppableProps} // 原代码漏传该属性,会导致拖拽失效
                >
                    {block.criteria.map((criterion, index) => (
                        <RatingTemplateCriterionRow
                            index={index}
                            key={criterion.id}
                            level={level + 1}
                            editCriterionTemplate={editCriterionTemplate}
                            criterion={criterion}
                            selected={selected}
                            handleCriterionCheckboxClick={handleCriterionCheckboxClick}
                        />
                    ))}
                    {block.children.map((childBlock, index) => (
                        <RatingTemplateBlockRow
                            index={index}
                            createBlockTemplate={createBlockTemplate}
                            editCriterionTemplate={editCriterionTemplate}
                            createCriterionTemplate={createCriterionTemplate}
                            key={childBlock.id}
                            block={childBlock}
                            level={level + 1}
                            setSelected={setSelected}
                            selected={selected}
                            toggleSelectedBlock={toggleSelectedBlock}
                            uncheckAllParentBlocks={uncheckAllParentBlocks}
                        />
                    ))}
                    {provided.placeholder}
                </TableBody>
            </React.Fragment>
        )
    }}
</Droppable>

该方案不会破坏原有表格样式继承,所有MUI表格的默认交互、样式都能正常生效,没有浏览器兼容问题。

方案2:给div容器添加display: contents样式(改动量最小)

如果不想替换现有标签,给非法的div容器添加display: contents CSS属性即可。该属性会让当前元素不生成任何渲染盒子,子元素直接提升到父层级参与布局,视觉上相当于这个div不存在,但DOM节点本身保留,可正常绑定ref、接收拖拽事件:

<div 
    ref={provided.innerRef}
    {...provided.droppableProps}
    style={{ display: 'contents' }}
>
    {/* 原有子元素逻辑保持不变 */}
</div>

注意:该方案不兼容IE11,不需要兼容老旧浏览器的场景可以直接用,只需要加一行样式。

方案3:ref转发挂载到合法表格节点(适配复杂嵌套场景)

如果表格嵌套层级极深,上述两个方案无法覆盖,可以通过React.forwardRef将Droppable需要的ref直接转发到合法的tr/td节点上,完全不引入额外的非法标签。该方案改动量较大,仅推荐结构特殊的复杂表格使用。

避坑提醒
  • 所有Droppable容器必须绑定provided.droppableProps,原代码只绑定了innerRef,会导致拖拽放置位置识别错误
  • 嵌套Droppable需要配置和外层不同的type属性,避免跨层级拖拽出现逻辑混乱
  • 每个Droppable内部的Draggable的index属性要取当前容器内的顺序索引,不要用全局索引,否则排序位置计算会出错

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 18:06:19