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

react-beautiful-dnd:开启isCombineEnabled=true时如何阻止元素移位?

阻止react-beautiful-dnd拖动时列表项移位(合并场景)

我需要实现客户合并功能:拖动重复的客户列表项到目标客户项上,将重复客户的订单转移到目标客户后删除重复项。参考react-beautiful-dnd的合并功能实现后,发现拖动过程中列表项会自动移位,希望所有列表项保持原位,只触发合并逻辑。

我的实现代码如下:

import {
    DragDropContext, Draggable, DraggableProvided, DraggableRubric, DraggableStateSnapshot,
    Droppable,
    DroppableProvided,
    DroppableStateSnapshot,
    DropResult,
    ResponderProvided
} from "react-beautiful-dnd"
import {useCallback} from 'react'

export default function FindDuplicateCustomersPage({customers}: Props) {
    const handleDragEnd = useCallback((result: DropResult, provided: ResponderProvided) => {
        console.log('drag end', result, provided)
        if(result.combine) {
            const {draggableId, combine} = result
            // 处理合并逻辑
            console.log(`合并 ${draggableId} 和 ${combine.draggableId}`)
            // 在这里添加合并业务代码
        } else {
            // 常规拖动结束逻辑(当前场景不需要)
        }
    }, [])

    return (
        <>
            <PageHeader>查找重复客户</PageHeader>
            <ContentWrap>
                <ContentSection title="合并与修复" noScroll>
                    {!customers.length ? (
                        <p>未找到重复客户。</p>
                    ) : (
                        <>
                            <p style={{marginBottom: '15px'}}>
                                将重复客户拖动到你想要合并的目标客户项上。重复客户的所有订单会转移到目标客户名下,
                                重复客户记录将被删除。
                            </p>
                            <DragDropContext onDragEnd={handleDragEnd}>
                                <Droppable droppableId="droppable" isCombineEnabled>
                                    {(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => (
                                        <ol className={css.customerList}
                                            ref={provided.innerRef} {...provided.droppableProps}>
                                            {customers.map((cust, idx) => (
                                                <Draggable key={cust.id} draggableId={cust.id} index={idx}>
                                                    {(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => (
                                                        <li
                                                            ref={provided.innerRef}
                                                            {...provided.draggableProps}
                                                            {...provided.dragHandleProps}
                                                            className={css.customer}
                                                        >
                                                            [{cust.number}] {cust.first_name} {cust.last_name}, {cust.email || '(无邮箱)'},
                                                            {cust.pri_phone_number || '(无电话)'},
                                                            订单数: {cust.booking_count}
                                                            <ExternalLink href={resolveRoute('customers.show', {id: cust.id})}>查看详情</ExternalLink>
                                                        </li>
                                                    )}
                                                </Draggable>
                                            ))}
                                            {provided.placeholder}
                                        </ol>
                                    )}
                                </Droppable>
                            </DragDropContext>
                        </>
                    )}
                </ContentSection>
            </ContentWrap>
        </>
    )
}

拖动时列表项会自动移位,如何让它们保持原位,只完成合并操作?


解决方案

要解决这个问题,核心是禁用Droppable的排序功能——默认Droppable会自动处理排序的位置调整,而我们只需要合并逻辑。具体修改如下:

  1. 给<Droppable>添加mode="virtual"属性,让它只响应合并事件,不处理排序的占位符和位置变化
  2. 将Draggable的index从循环的idx改为固定的客户ID,避免拖动时index变化导致的位置重排
  3. 移除provided.placeholder,虚拟模式下不需要这个占位符,它会导致布局移位
  4. 可以直接删掉handleDragEnd里的else分支,因为我们不需要处理排序逻辑

修改后的核心代码片段:

<DragDropContext onDragEnd={handleDragEnd}>
  <Droppable droppableId="droppable" isCombineEnabled mode="virtual">
    {(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => (
      <ol className={css.customerList}
          ref={provided.innerRef} {...provided.droppableProps}>
        {customers.map((cust) => (
          <Draggable key={cust.id} draggableId={cust.id} index={cust.id}>
            {(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => (
              <li
                ref={provided.innerRef}
                {...provided.draggableProps}
                {...provided.dragHandleProps}
                className={css.customer}
              >
                [{cust.number}] {cust.first_name} {cust.last_name}, {cust.email || '(无邮箱)'},
                {cust.pri_phone_number || '(无电话)'},
                订单数: {cust.booking_count}
                <ExternalLink href={resolveRoute('customers.show', {id: cust.id})}>查看详情</ExternalLink>
              </li>
            )}
          </Draggable>
        ))}
      </ol>
    )}
  </Droppable>
</DragDropContext>

补充说明

  • mode="virtual"是关键:这个模式下Droppable不会自动调整列表项位置,仅监听合并事件
  • 用客户ID作为index:保证每个Draggable的标识固定,不会因为拖动顺序变化触发重排
  • 移除占位符:虚拟模式下占位符无作用,反而会导致列表布局出现空白移位

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 04:00:57