完全指南:draggableId 与 droppableId 的规则、约束与内部实现)
react-beautiful-dnd 标识符Identifiers完全指南draggableId 与 droppableId 的规则、约束与内部实现【免费下载链接】react-beautiful-dndBeautiful and accessible drag and drop for lists with React项目地址: https://gitcode.com/gh_mirrors/re/react-beautiful-dnd导读draggableId与droppableId是 react-beautiful-dnd 中Draggable /与Droppable /的身份凭证也是整个拖拽引擎做维度收集、命中判定与状态更新的核心索引。本文以官方文档 identifiers.md 为主体结合仓库源码注册表、校验逻辑、DOM 属性注入深入讲解 id 的字符串约束、全局唯一性要求、不要用索引当 id 的底层原因以及为什么这些规则是出于简化与性能的设计取舍。读完本文你将能写出稳定、无异常的拖拽列表 id 方案。一、什么是 draggableId 与 droppableId在 react-beautiful-dnd 中每个Draggable /与Droppable /都必须提供一个 idDraggable /的draggableId标识一个可被拖拽的项目Droppable /的droppableId标识一个可以接收拖拽的容器列表。这两个 id 分别作为对应组件的必填 prop 传入例如import { DragDropContext, Droppable, Draggable } from react-beautiful-dnd; DragDropContext onDragEnd{onDragEnd} Droppable droppableIdlist-1 {(provided) ( ul ref{provided.innerRef} {...provided.droppableProps} {items.map((item, index) ( Draggable key{item.id} draggableId{item.id} index{index} {(dragProvided) ( li ref{dragProvided.innerRef} {...dragProvided.draggableProps} {item.content} /li )} /Draggable ))} {provided.placeholder} /ul )} /Droppable /DragDropContext在类型层面id 被定义为字符串类型src/types.js 中给出了公开类型别名export type Id string; export type DraggableId Id; export type DroppableId Id;官方 types 指南 中同样以 Flow 类型形式声明了Id、TypeId、DroppableId、DraggableId四个别名它们共同参与构建DraggableLocation、Combine、DragStart、DropResult等公开类型。因此你在onDragEnd等回调中拿到的result.draggableId、result.source.droppableId等字段全部都是字符串 id。二、id 必须是字符串String官方文档明确规定期望 id 是一个string。这一要求不仅停留在类型层面还在运行时强制校验——如果传入非字符串 idreact-beautiful-dnd 会直接抛出异常throw an error。在 Draggable 的 prop 校验 中const id props.draggableId; invariant(id, Draggable requires a draggableId); invariant( typeof id string, Draggable requires a [string] draggableId. Provided: [type: ${typeof id}] (value: ${id}), );在 Droppable 的 prop 校验 中invariant(props.droppableId, A Droppable requires a droppableId prop); invariant( typeof props.droppableId string, A Droppable requires a [string] droppableId. Provided: [${typeof props.droppableId}], );这两段校验都运行在开发模式的 setup warning 阶段useDevSetupWarning也就是说你在开发环境就能立刻发现 id 类型错误而不是等到拖拽行为异常才排查。不要使用数字或其他类型的值作为 id即使 JavaScript 的对象键会自动把数字强制转成字符串也应遵守规范显式传入字符串。三、id 必须在 DragDropContext 内全局唯一文档强调一个 id 必须在同一个DragDropContext /内唯一地标识一个Draggable /或Droppable /。具体包含两层含义跨列表唯一即使你有多个相互连接的列表每个Droppable /的 id、每个Draggable /的 id 都必须唯一不能因为项目位于不同列表而重复使用同一个 id。跨 type 唯一即使两个Droppable /的typeprop 不同它们的droppableId也不能相同。这一要求可以从注册表registry的实现得到印证。react-beautiful-dnd 使用 create-registry.js 维护所有组件条目其内部数据结构是以 id 为键的对象映射const entries: EntryMap { draggables: {}, // { [draggableId]: DraggableEntry } droppables: {}, // { [droppableId]: DroppableEntry } };register操作本质就是一次对象赋值register: (entry: DraggableEntry) { entries.draggables[entry.descriptor.id] entry; notify({ type: ADDITION, value: entry }); },同理droppableAPI.register执行entries.droppables[entry.descriptor.id] entry。在这种以 id 为键的映射结构下重复的 id 会直接互相覆盖导致先注册的组件被后注册的组件顶掉从而引发找不到条目拖拽维度错误等连锁问题。所以文档才会强调唯一性——这不是审美要求而是数据结构决定的硬性约束。此外registry-types.js 显示每个注册条目由uniqueId组件内部自增的唯一编号与descriptor描述符含 id组成用于区分同一个组件更新与另一个组件复用了 id。四、id 如何进入 DOMdata 属性透传id 不仅仅存在于 React 状态中它还会被写入真实 DOM 节点供库内部通过 DOM 查询定位组件。以 Draggable 的实现 为例draggableProps会携带draggableProps: { data-rbd-draggable-context-id: contextId, data-rbd-draggable-id: draggableId, style, onTransitionEnd, },而 Droppable 的实现 同样会注入data-rbd-droppable-id: droppableId,对应的类型声明可见 draggable-types.js 与 droppable-types.js。库在启动拖拽时会通过findDraggable、findDragHandle等工具函数见 get-elements依据这些 data 属性定位真实的拖拽节点。这意味着id 必须能被安全地放进 HTML 属性这也是要求 id 为字符串、且避免使用容易产生特殊字符的值的原因之一。五、避免复用 id为什么不要用 index 当 id官方文档给出了一条最重要的实战建议不要基于 index 构造 draggableId 或 droppableId。Dont base an id on a index最佳实践是把一个 id 与一条数据data关联起来在重排reorder之间不更新它。例如上面示例中的draggableId{item.id}其中item.id是数据本身的稳定主键。当然官方也说明除了拖拽进行中during a drag你随时可以更改draggableId或droppableId包括重排之后。但为了避免异常必须避免在两个组件之间复用 id——而基于 index 生成 id 正是触发这种复用的典型场景。5.1 内部发生了什么以 droppableId 变更为例文档用一个三步示例展示了内部引用变更过程这里完整保留并加以解释步骤 1更新 Droppable旧 droppableIddroppable-0新 droppableIddroppable-1 删除对droppable-0的引用 添加对droppable-1的引用步骤 2更新 Droppable隐患出现旧 droppableIddroppable-1这正是上一步刚注册的新 id新 droppableIddroppable-2 删除对droppable-1的引用 会误删掉我们刚注册的droppable-1 添加对droppable-2的引用步骤 3更新 Droppable异常爆发旧 droppableIddroppable-1该引用已在步骤 2 被删除新 droppableIddroppable-5 删除对droppable-1的引用 因droppable-1已不存在而抛出异常5.2 源码印证unregister 的删除逻辑上述删除引用在源码中对应注册表的unregister操作。create-registry.js 中 Draggable 的注销逻辑为unregister: (entry: DraggableEntry) { const draggableId: DraggableId entry.descriptor.id; const current findDraggableById(draggableId); // 可能已被 clean 提前移除 if (!current) { return; } // uniqueId 不匹配说明是过期的注册记录 if (entry.uniqueId ! current.uniqueId) { return; } delete entries.draggables[draggableId]; notify({ type: REMOVAL, value: entry }); },delete entries.draggables[draggableId]正是删除引用。当一个基于 index 的 id 从 1 变到 2 时组件卸载流程会把droppable-1删掉而此时新的、恰好也叫droppable-1的组件可能刚刚注册——旧组件的注销会误删新组件的引用最终在第三步出现Cannot find droppable entry with id之类的 invariant 异常见 getDroppableByIdinvariant(entry, Cannot find droppable entry with id [${id}]);同理Draggable 的getDraggableById也会在找不到条目时抛出Cannot find draggable entry with id [...]。5.3 拖拽期间的额外约束值得补充的是虽然非拖拽期间可以改 id但拖拽期间修改 id 是禁止的。从 dimension-marshal.js 的shouldPublishUpdate可以看到库会对拖拽中新增/移除的 Draggable 进行严格检查除非是虚拟列表virtual mode否则会给出警告并拒绝发布这些变更。因此把 id 与数据强绑定、保持 id 稳定是规避一切异常的最省心方案。六、这些规则可以改变吗文档明确表示可以。作者承认这些约束并非绝对必要而是为了简化与性能for simplicity and performance做出的取舍简化以 id 为键的对象映射entries.draggables[id]、entries.droppables[id]让注册、查询、注销都变成 O(1) 的常数时间操作代码路径清晰直观性能拖拽过程中尤其是 dimension-marshal 与 while-dragging-publisher会频繁按 id 查找条目与比对描述符扁平映射比遍历数组高效得多。如果你对这套规则有强烈意见官方欢迎在 GitHub issue 中继续讨论。不过在实践中绝大多数应用采用id 数据主键的模式即可完美规避所有限制无需触及这一层设计决策。七、最佳实践速查综合文档与源码给出如下 checklist规则说明违反后果id 必须是字符串传入非字符串会触发 invariant 异常draggable 校验、droppable 校验开发环境直接抛错同一 DragDropContext 内全局唯一包括跨列表、跨type注册表以 id 为键重复会覆盖见 create-registry.js条目互相覆盖、找不到条目不要基于 index 构造 id重排会改变 index导致 id 迁移并误删新引用Cannot find ... entry with id异常id 与数据绑定重排不更新draggableId{item.id}是最稳妥写法状态错乱、异常拖拽期间不要改 id拖拽中变更会被 dimension-marshal 拒绝虚拟列表除外见 dimension-marshal.js警告或被忽略注意 id 会写入 DOM data 属性见 draggable.jsx 与 droppable.jsx 的data-rbd-*-id影响内部 DOM 查询定位一句话总结把 id 当作数据的主键来对待——稳定、唯一、始终是字符串你的拖拽列表就不会在 id 上踩坑。【免费下载链接】react-beautiful-dndBeautiful and accessible drag and drop for lists with React项目地址: https://gitcode.com/gh_mirrors/re/react-beautiful-dnd创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考