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
相关产品推荐
相关产品推荐

