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

