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

Dnd-kit拖拽组件动画过慢及页面卡顿问题排查求助

拖拽组件长期使用卡顿问题排查与优化方案

我开发的页面会渲染多组包含DndContext的Vaga组件,每个组件内有大量可拖拽卡片。页面刚刷新时功能正常,但使用时间越长越卡顿;奇怪的是在Chrome DevTools性能模式下运行时却没有卡顿问题。

当尝试将左侧卡片拖拽到右侧三个可放置容器时,卡片动画严重卡顿,几乎无法移动。页面每行都是一个带DndContext的Vaga组件,实际数量远多于截图所示。已尝试用memo优化Card组件重渲染,但未解决问题。


页面渲染Vaga组件的代码

{vacancies.length > 0 ? (
  vagasFiltradas.map((vacancy) => (
    <div className="vagas_container" key={vacancy.id}>
      <HotNeedsProvider>
        <Vaga dataVaga={vacancy} />
      </HotNeedsProvider>
    </div>
  ))
) : ('')}

Vaga组件代码

<div className='hotNeeds-container-dnd'>
  <DndContext
    sensors={sensors}
    collisionDetection={closestCenter}                              
    onDragStart={handleDragStart}
    onDragOver={handleDragOver} 
    onDragEnd={(handleDragEnd)}                             
   >
     {vagas && (
        <>
           <Indicados/>
            <Candidatos
              data={initialDataVaga}
               loadCandidatos={load}
                dndContainerId={'candidatos'}
              />
          </>
      )}                        
        <DragOverlay >
          {activeId ? (
            <Card id={activeId} info={overlayData} />
           ): null}
        </DragOverlay>
  </DndContext>
</div>

Indicados和Candidatos组件内的SortableContext代码

<SortableContext
 id={dndContainerId}
 items={vagas[candidateType]}
 strategy={horizontalListSortingStrategy}
>
 <div className="candidatos-container-body" ref={setNodeRef}>
     {isLoading ? (
       <p>Loading...</p>
     ) : (vagas && vagas[candidateType]?.length > 0) ? (
         vagas[candidateType].map((candidato) => (
           <Card
             key={candidato.id}
             id={candidato.id}
             info={candidato}
             type={candidateType}
             stage={'indicacoes'}
           />
         ))
     ) : (
       <p>No candidates found</p>
     )}
 </div>
</SortableContext>

已优化的Card组件代码

export const Card = memo(({id,info,type, stage}) => {

  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition
  } = useSortable({ id: info.id })

const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    touchAction: "none",
  }

  return (
    <div
      className="card-container"
      ref={setNodeRef}   
      style={style}
      {...attributes}
      {...listeners}
    >
    </div>
  )
})

可能的优化方向

1. 全局单例DndContext替代多实例

当前每个Vaga组件都创建独立的DndContext,大量实例会导致事件监听累积、内存占用飙升。建议将DndContext提升到页面最外层,所有拖拽相关组件共享同一个上下文:

// 页面根组件
<DndContext
  sensors={sensors}
  collisionDetection={closestCenter}                              
  onDragStart={handleDragStart}
  onDragOver={handleDragOver} 
  onDragEnd={(handleDragEnd)}                             
>
  {vacancies.length > 0 ? (
    vagasFiltradas.map((vacancy) => (
      <div className="vagas_container" key={vacancy.id}>
        <HotNeedsProvider>
          <Vaga dataVaga={vacancy} />
        </HotNeedsProvider>
      </div>
    ))
  ) : ('')}
</DndContext>

2. 优化SortableContext的items稳定性

确保items数组引用稳定,避免每次渲染生成新数组导致SortableContext重渲染。可以用useMemo缓存:

const sortedItems = useMemo(() => vagas[candidateType] || [], [vagas, candidateType])

<SortableContext
 id={dndContainerId}
 items={sortedItems}
 strategy={horizontalListSortingStrategy}
>
  {/* 原有内容 */}
</SortableContext>

3. 拖拽时暂停非必要渲染

拖拽过程中,非当前拖拽区域的组件可以暂时停止更新。通过DndContext的回调设置全局状态,控制非拖拽区域组件的渲染:

// 全局状态
const [isDragging, setIsDragging] = useState(false)
const [currentDraggingVagaId, setCurrentDraggingVagaId] = useState(null)

// DndContext的回调
const handleDragStart = (event) => {
  setIsDragging(true)
  setCurrentDraggingVagaId(getCurrentVagaId(event)) // 自定义逻辑获取当前拖拽的Vaga ID
}
const handleDragEnd = () => {
  setIsDragging(false)
  setCurrentDraggingVagaId(null)
}

// 在Vaga组件中控制渲染
{!isDragging || currentDraggingVagaId === vacancy.id ? (
  <>
    <Indicados/>
    <Candidatos {...props} />
  </>
) : (
  // 渲染静态占位或简化版本
  <div className="vagas-static-placeholder"></div>
)}

4. 排查内存泄漏

  • 检查handleDragStart、handleDragOver等回调是否存在闭包引用,导致组件卸载后仍被持有。
  • 使用Chrome DevTools的Memory面板,多次拖拽后拍摄堆快照,排查是否有大量未释放的Card、SortableContext实例。

5. 轻量化DragOverlay组件

确保DragOverlay内的卡片仅保留拖拽必要的样式和结构,避免不必要的计算:

<DragOverlay>
  {activeId ? (
    <DragCard id={activeId} info={overlayData} />
  ): null}
</DragOverlay>

// 仅保留拖拽必要逻辑的简化卡片组件
const DragCard = memo(({id, info}) => {
  const { transform, transition } = useSortable({ id: info.id })
  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    touchAction: "none",
  }
  return <div className="drag-card-container" style={style}>{info.name}</div>
})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 13:04:53