尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
react-beautiful-dnd 表格行拖拽排序实战:固定布局、尺寸锁定与 Reparenting 完整指南
react-beautiful-dnd 表格行拖拽排序实战固定布局、尺寸锁定与 Reparenting 完整指南【免费下载链接】react-beautiful-dndBeautiful and accessible drag and drop for lists with React项目地址: https://gitcode.com/gh_mirrors/re/react-beautiful-dnd导读table是展示表格数据最自然、兼容性最好的语义化容器而 react-beautiful-dnd 又恰好不要求为Draggable /和Droppable /增加任何额外包裹元素——这意味着你可以写出既是合法 HTML、又支持拖拽排序的表格。本文以仓库文档 docs/patterns/tables.md 为主体结合 stories/src/table 目录下的四个可运行示例系统讲解表格行重排的两种策略固定布局、尺寸锁定以及进阶的 Reparenting克隆 API / 自定义 Portal方案并给出源码级原理帮助你直接落地可复制、可运行的表格拖拽实现。为什么table也能用 react-beautiful-dndreact-beautiful-dnd 的核心约束是Draggable /与Droppable /必须在 DOM 中真实渲染、且innerRef能拿到对应的 DOM 节点。与需要额外div包裹的库不同react-beautiful-dnd 允许你把ref和...provided的属性直接铺到tr上因此使用table的好处提供方展示表格数据的干净方式浏览器极佳的浏览器兼容性浏览器可直接把表格复制粘贴到其他应用浏览器可以重排行内项目react-beautiful-dnd关于列重排的说明截至当前版本社区尚未找到实现表格列语义化重排的方案。原因在于 HTML 中没有一个元素能单独代表一列——列只是多行中单元格对齐的结果无法把一个Draggable /包在列外面使其可拖。如果你找到了可行方案欢迎向本指南提交 PR。基础骨架把Droppable /和Draggable /放进表格在进入策略细节前先看最基本的结构。以 stories/src/table/with-dimension-locking.jsx 为例table使用table-layout属性可在auto/fixed间切换Droppable droppableIdtable的droppableProvided.innerRef绑定到tbody上每一行由一个Draggable draggableId{quote.id} index{index}包裹渲染时把provided.innerRef、provided.draggableProps、provided.dragHandleProps全部铺到tr上droppableProvided.placeholder放在tbody末尾用于占位保证拖拽时其他行让位onDragEnd中使用stories/src/reorder.js的reorder(list, startIndex, endIndex)完成数组重排。Table layout{this.state.layout} THead tr thAuthor/th thContent/th /tr /THead Droppable droppableIdtable {(droppableProvided) ( TBody ref{(ref) { this.tableRef ref; droppableProvided.innerRef(ref); }} {...droppableProvided.droppableProps} {this.state.quotes.map((quote, index) ( Draggable draggableId{quote.id} index{index} key{quote.id} {(provided, snapshot) ( TableRow provided{provided} snapshot{snapshot} quote{quote} / )} /Draggable ))} {droppableProvided.placeholder} /TBody )} /Droppable /Tablereorder的实现在 stories/src/reorder.js通过splice取出源项再插入目标位置是onDragEnd中同步更新列表状态的常规工具。策略一固定布局更快、更简单适用前提列宽不随内容变化该策略要求列的宽度是固定的——即无论单元格里放什么内容列宽都不变。两种方式都能满足使用table-layout: fixed列宽由首行或显式设置决定使用table-layout: auto但手动设置每个单元格的宽度例如width: 50%。核心做法拖拽时给tr加display: table实现上你唯一需要做的就是在行被拖拽期间给tr设置display: table。在 stories/src/table/with-fixed-columns.jsx 中可以看到通过snapshot.isDragging动态拼接样式const Row styled.tr ${(props) props.isDragging ? background: ${colors.G100}; /* maintain cell width while dragging */ display: table; : }; ; const Cell styled.td box-sizing: border-box; padding: ${grid}px; /* locking the width of the cells */ width: 50%; ;拖拽时 react-beautiful-dnd 会给行应用position: fixed之类的变换样式display: table能让该行继续按表格规则计算宽度配合单元格显式的width: 50%从而保持列宽不塌陷。已知问题与替代方案部分用户反馈table-layoutdisplay: table的方案在元素拖拽期间样式不稳定。替代做法是拖拽时既不设置table-layout也不设置display: table而是永久性地给每个td设置固定宽度例如内联样式width: 100px或 CSS。这样完全不需要任何事件回调代码更简单拖拽时样式也不会丢。策略二尺寸锁定更稳健但更慢原理拖拽会破坏表格的自动列宽计算表格的自动列宽table-layout: auto依赖所有单元格共同参与计算。当拖拽开始时 react-beautiful-dnd 会给被拖行应用position: fixed使其脱离表格列宽计算的参与导致列宽突变。因此需要在拖拽开始前用内联样式把所有单元格的宽高锁死避免列尺寸变化。用onBeforeDragStart触发锁定根据 docs/guides/responders.md 的说明onBeforeDragStart在拖拽即将开始、且所有Draggable /与Droppable /的尺寸已经从 DOM 采集完毕后被调用。这个时机恰好适合做表格重排所需的尺寸锁定该文档明确点名了 tables.md 的用法。在 stories/src/table/with-dimension-locking.jsx 中onBeforeDragStart只负责把应用状态切换为拖拽中示例中通过IsDraggingContext广播给每个单元格onBeforeDragStart () { this.setState({ isDragging: true }); }; onDragEnd (result) { this.setState({ isDragging: false }); // ...根据 result 调用 reorder 并 setState };单元格如何锁定尺寸getSnapshotBeforeUpdatecomponentDidUpdate真正的锁定发生在TableCell组件内部利用 React 的getSnapshotBeforeUpdate在 DOM 变更前读取尺寸快照再在componentDidUpdate里写入内联样式getSnapshotBeforeUpdate(prevProps) { if (!this.ref) return null; const isDragStarting this.props.isDragOccurring !prevProps.isDragOccurring; if (!isDragStarting) return null; const { width, height } this.ref.getBoundingClientRect(); return { width, height }; } componentDidUpdate(prevProps, prevState, snapshot) { const ref this.ref; if (!ref) return; if (snapshot) { if (ref.style.width snapshot.width) return; ref.style.width ${snapshot.width}px; ref.style.height ${snapshot.height}px; return; } if (this.props.isDragOccurring) return; // inline styles not applied if (ref.style.width null) return; // no snapshot and drag is finished - clear the inline styles ref.style.removeProperty(height); ref.style.removeProperty(width); }关键点拖拽开始的瞬间isDragOccurring从 false → true读取getBoundingClientRect()得到宽高快照写入内联样式锁定拖拽结束后isDragOccurring为 false 且没有新快照清理掉内联的宽高恢复表格自动计算整个流程不需要任何事件监听器完全由 React 生命周期驱动。性能特征与适用规模该策略在规模变大时性能较差因为它要求对每一行调用render()对每一行读取 DOMgetBoundingClientRect即文档中所说的window.getComputedStyles类读操作对于少于 50 行的表格这种方案完全够用更大的表格建议改用固定布局策略或考虑虚拟列表方案。进阶Reparenting克隆 API / 自定义 Portal如果你需要在表格行重排时使用 reparenting克隆或你自己的 Portal需要额外几步。建议先阅读 docs/guides/reparenting.md 了解整体思路。为什么需要 reparentingreact-beautiful-dnd 默认把元素留在原 DOM 位置仅通过position: fixed移动它。但position: fixed会受到祖先transform的影响导致拖拽定位错误。解决方式是把拖拽项移动到document.body或其直接后代这个新的父容器即 Portal中。必须掌握的 React 挂载时序在 React 中把一个已存在的tr移入ReactDOM.createPortal时旧tr会被卸载、新tr会挂载进 Portal顺序是旧tr执行componentWillUnmount新tr执行componentWillMount为了保留被移动行的单元格尺寸需要按策略二的方式用内联样式锁定尺寸。难点在于新组件无法直接拿到移动前那个组件的信息所以必须在旧tr卸载时把单元格尺寸读出来存到组件外部等新tr在componentDidMount挂载后再重新应用。还要注意一个坑componentDidMount被调用时你无法确定这次挂载是因为行不再需要、正在卸载还是因为即将移入 Portal。所以必须显式区分这两种情况。官方推荐的实现步骤在tr的componentWillUnmount中从 DOM 读取当前各单元格的宽高存入组件外部的存储如模块级snapshotMap供后续挂载的新组件读取新组件挂载时若DraggableStateSnapshot.isDragging为 true就去查之前记录的宽度存在则应用该宽度。示例一克隆 APIrenderClonegetContainerForClone克隆 API 是 reparenting 的一等公民方案拖拽期间原Draggable /被移除由renderClone渲染的克隆体进入getContainerForClone返回的容器。在 stories/src/table/with-clone.jsx 中可以看到完整的表格版实现。首先因为要把tr挂进 PortalReact 会对非表格元素内挂tr给出警告所以示例创建了一个隐藏的空表格作为 Portal 容器// Using a table as the portal so that we do not get react // warnings when mounting a tr element const table document.createElement(table); table.classList.add(my-super-cool-table-portal); Object.assign(table.style, { margin: 0, padding: 0, border: 0, height: 0, width: 0, }); const tbody document.createElement(tbody); table.appendChild(tbody); document.body.appendChild(table);Droppable侧配置renderClone与getContainerForCloneDroppable droppableIdtable renderClone{(provided, snapshot, rubric) ( TableRow provided{provided} snapshot{snapshot} quote{this.state.quotes[rubric.source.index]} / )} getContainerForClone{() tbody} TableCell组件配合模块级snapshotMap完成尺寸的存取const snapshotMap {}; class TableCell extends React.Component { componentDidMount() { const cellId this.props.cellId; if (!snapshotMap[cellId]) return; if (!this.props.isDragging) { // cleanup the map if it is not being used delete snapshotMap[cellId]; return; } this.applySnapshot(snapshotMap[cellId]); } componentWillUnmount() { const snapshot this.getSnapshot(); if (!snapshot) return; snapshotMap[this.props.cellId] snapshot; } getSnapshot () { if (!this.ref) return null; const { width, height } this.ref.getBoundingClientRect(); return { width, height }; }; applySnapshot (snapshot) { const ref this.ref; if (!ref) return; if (ref.style.width snapshot.width) return; ref.style.width ${snapshot.width}px; ref.style.height ${snapshot.height}px; }; }数据流是onBeforeDragStart把isDragging置 true通过IsDraggingContext广播拖拽开始时单元格在getSnapshotBeforeUpdate读取宽高、componentDidUpdate写入内联样式同策略二原tr卸载时componentWillUnmount把宽高写入snapshotMap克隆体在 Portal 中挂载时componentDidMount从snapshotMap取回宽高并应用从而让 Portal 中的克隆行保持与源表格一致的列宽。renderClone的类型签名见 docs/guides/reparenting.mdrenderClone: ?DraggableChildrenFn其中DraggableChildrenFn (Provided, StateSnapshot, DraggableRubric) Node | null与Draggable /的 children 函数类型完全一致getContainerForClone: () HTMLElement若不定义则默认使用document.body对应源码 src/view/droppable/connected-droppable.js 中的getContainerForClone: getBody。示例二自定义 PortalReactDOM.createPortal如果不使用克隆 API也可以在Draggable /内部自行调用ReactDOM.createPortal。stories/src/table/with-portal.jsx 展示了这种做法当snapshot.isDragging为 true 时把整个tr通过ReactDOM.createPortal(child, tbody)移入预先创建的隐藏表格其余TableCell的尺寸存取逻辑与克隆示例完全一致。if (!snapshot.isDragging) { return child; } return ReactDOM.createPortal(child, tbody);需要注意同样见 docs/guides/reparenting.md 的性能警告任何被 reparenting 的元素都会从头重新渲染不要把大型组件树移入 Portal否则会出现明显的 UI 卡顿官方因此不推荐默认使用 reparenting。可运行示例一览仓库在 stories/10-table.stories.js 中注册了完整的 Storybook 故事可直接对照阅读源码Story 名称对应实现文件核心要点with fixed width columnsstories/src/table/with-fixed-columns.jsx固定列宽 拖拽时display: tablewith dimension lockingstories/src/table/with-dimension-locking.jsxgetSnapshotBeforeUpdate锁定全部单元格尺寸with clonestories/src/table/with-clone.jsxrenderClonegetContainerForClonesnapshotMapwith custom portalstories/src/table/with-portal.jsxReactDOM.createPortalsnapshotMap相关文档与资源响应器生命周期与onBeforeDragStart的时机与限制docs/guides/responders.mdReparenting 的背景、克隆 API 与 Portal 方案docs/guides/reparenting.md虚拟列表场景下克隆 API 的必用性docs/patterns/virtual-lists.md小结在 react-beautiful-dnd 中重排表格行优先根据列宽是否固定二选一固定布局方案快而简单拖拽时display: table 显式列宽即可尺寸锁定方案用onBeforeDragStart配合getSnapshotBeforeUpdate/componentDidUpdate锁死所有单元格宽高稳健但需要每行渲染和 DOM 读取适合 50 行以内的表格。若涉及克隆或 Portal 的 reparenting则务必掌握componentWillUnmount→componentWillMount的时序用模块级snapshotMap完成单元格尺寸的交接克隆场景优先使用renderClonegetContainerForClone的一等公民 API。【免费下载链接】react-beautiful-dndBeautiful and accessible drag and drop for lists with React项目地址: https://gitcode.com/gh_mirrors/re/react-beautiful-dnd创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

使用 PyInstaller 打包 PaddleOCR 项目:从环境准备到可执行程序发布

使用 PyInstaller 打包 PaddleOCR 项目:从环境准备到可执行程序发布

使用 PyInstaller 打包 PaddleOCR 项目:从环境准备到可执行程序发布 【免费下载链接】PaddleOCR 飞桨多语言OCR工具包(实用超轻量OCR系统,支持80种语言识别,提供数据标注与合成工具,支持服务器、移动端、嵌入式及IoT设…

📅 2026/9/19 5:58:10
Agent Discovery 实战指南:用 agent-governance-toolkit 发现并治理组织内的 Shadow AI

Agent Discovery 实战指南:用 agent-governance-toolkit 发现并治理组织内的 Shadow AI

Agent Discovery 实战指南:用 agent-governance-toolkit 发现并治理组织内的 Shadow AI 【免费下载链接】agent-governance-toolkit AI Agent Governance Toolkit — Policy enforcement, zero-trust identity, execution sandboxing, and reliability engineering …

📅 2026/9/19 5:58:10
从零训练PaddleOCR模型:数据标注、配置调优与部署全流程实战

从零训练PaddleOCR模型:数据标注、配置调优与部署全流程实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/19 5:53:10
MORE NEWS

更多资讯

📰

Python文件操作与编码处理实战指南

1. Python文件操作基础与编码原理作为一名长期使用Python处理各种文件格式的开发工程师,我深刻理解文件操作和编码处理在项目开发中的重要性。无论是处理日志文件、配置文件还是用户上传的数据,掌握Python文件操作的核心方法能极大提升开发效率。1.1 文件…

📰

PixiJS v8 项目脚手架实战指南:用 create-pixi CLI 从零搭建与集成 WebGL/WebGPU 应用

PixiJS v8 项目脚手架实战指南:用 create-pixi CLI 从零搭建与集成 WebGL/WebGPU 应用 【免费下载链接】pixijs The HTML5 Creation Engine: Create beautiful digital content with the fastest, most flexible 2D WebGL renderer. 项目地址: https://gitcode.co…

📰

OpenSkills install命令全解析:GitHub、本地路径、私有仓库4种安装方式

OpenSkills install命令全解析:GitHub、本地路径、私有仓库4种安装方式 【免费下载链接】openskills Universal skills loader for AI coding agents - npm i -g openskills 项目地址: https://gitcode.com/gh_mirrors/op/openskills OpenSkills 是一款面向 …

📰

Zephyr native_sim 交叉编译实战:在 x86_64 主机上构建并运行 ARM 目标可执行文件

Zephyr native_sim 交叉编译实战:在 x86_64 主机上构建并运行 ARM 目标可执行文件 【免费下载链接】zephyr Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures. …

📰

RealSense深度后处理一文看懂:5个滤镜+参数调优实践

RealSense深度后处理一文看懂:5个滤镜参数调优实践 【免费下载链接】librealsense RealSense SDK 项目地址: https://gitcode.com/GitHub_Trending/li/librealsense librealsense(RealSense SDK)为 RealSense 深度相机提供了一组独立可…

📰

PiXYZ Plugin实战:工业CAD模型导入Unity与轻量化处理指南

1. 工业模型进Unity,为什么PiXYZ是绕不开的一环做过数字孪生项目的人都有一个共同体会:真正花时间的往往不是写Shader、搭UI、调灯光,而是把甲方丢过来的那一堆CAD模型弄进引擎里。一个中等规模的工厂场景,原始STEP或JT文件动辄几…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬