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

如何在dndkit拖拽时减少/阻止SortableItem重渲染

dnd-kit拖拽列表重渲染优化方案

核心问题分析

你遇到的频繁重渲染问题,本质是useSortable钩子默认会订阅SortableContext的全局状态变化(如拖拽激活项、悬停项),导致所有列表项跟随重渲染。之前用memo无效,是因为没有限制钩子的订阅范围,且传递了不必要的可变props(如index)。

优化方案

以下是可落地的分层优化步骤,兼顾动画效果与性能:

1. 限制useSortable的状态订阅范围

通过useSortable的select配置,只订阅当前列表项相关的状态,避免全局状态变化触发所有组件重渲染:

const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
  id,
  // 仅订阅当前item的状态,减少重渲染触发条件
  select: (state) => ({
    isDragging: state.active.id === id,
    transform: state.items[id]?.transform,
    transition: state.items[id]?.transition,
  }),
});

2. 正确使用React.memo+稳定props

  • 移除index这类可变props,仅传递稳定的id作为唯一标识
  • 用memo包裹拖拽组件,阻止无意义的props变化重渲染
  • 用useMemo缓存样式对象,避免每次渲染创建新引用

3. 拆分组件:分离拖拽逻辑与展示逻辑

将组件拆分为拖拽容器和纯展示内容两部分,让复杂的展示组件仅在必要时重渲染:

// 纯展示组件,仅在isDragging变化时重渲染
const ItemContent = memo(({ id, isDragging }) => {
  console.log(`rendered content: ${id}`);
  return (
    <div className={`p-3 ${isDragging ? 'opacity-50' : ''}`}>
      {id}
    </div>
  );
});

// 拖拽容器组件,仅处理拖拽逻辑
const SortableItem = memo(({ id }) => {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
    id,
    select: (state) => ({
      isDragging: state.active.id === id,
      transform: state.items[id]?.transform,
      transition: state.items[id]?.transition,
    }),
  });

  const style = useMemo(() => ({
    transform: transform ? CSS.Transform.toString(transform) : undefined,
    transition,
  }), [transform, transition]);

  return (
    <div
      className="border border-red-500 bg-yellow-50 mb-2"
      ref={setNodeRef}
      style={style}
      {...attributes}
      {...listeners}
    >
      <ItemContent id={id} isDragging={isDragging} />
    </div>
  );
});

4. 优化父组件逻辑

移除不必要的props传递,确保items数组仅在拖拽结束时更新:

// App组件中map渲染时,仅传递id
{items.map((item) => (
  <SortableItem key={item} id={item} />
))}

完整优化代码

import { useState, memo, useMemo } from "react";
import { DndContext, closestCenter } from "@dnd-kit/core";
import {
  arrayMove,
  SortableContext,
  verticalListSortingStrategy,
  useSortable,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";

export function App() {
  const [items, setItems] = useState(["Item1", "Item2", "Item3"]);

  const handleDragEnd = (event) => {
    const { active, over } = event;

    if (active.id !== over.id) {
      setItems((prevItems) => {
        const activeIndex = prevItems.indexOf(active.id);
        const overIndex = prevItems.indexOf(over.id);
        return arrayMove(prevItems, activeIndex, overIndex);
      });
    }
  };

  return (
    <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
      <div className="p-3">
        <SortableContext
          items={items}
          strategy={verticalListSortingStrategy}
        >
          {items.map((item) => (
            <SortableItem key={item} id={item} />
          ))}
        </SortableContext>
      </div>
    </DndContext>
  );
}

const ItemContent = memo(({ id, isDragging }) => {
  console.log(`rendered content: ${id}`);
  return (
    <div className={`p-3 ${isDragging ? 'opacity-50' : ''}`}>
      {id}
    </div>
  );
});

const SortableItem = memo(({ id }) => {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
    id,
    select: (state) => ({
      isDragging: state.active.id === id,
      transform: state.items[id]?.transform,
      transition: state.items[id]?.transition,
    }),
  });

  const style = useMemo(() => ({
    transform: transform ? CSS.Transform.toString(transform) : undefined,
    transition,
  }), [transform, transition]);

  console.log(`rendered container: ${id}`);

  return (
    <div
      className="border border-red-500 bg-yellow-50 mb-2"
      ref={setNodeRef}
      style={style}
      {...attributes}
      {...listeners}
    >
      <ItemContent id={id} isDragging={isDragging} />
    </div>
  );
});

是否值得投入精力?

  • 如果列表项数量少(<50个)且结构简单:优化收益低,无需投入
  • 如果列表项数量多(>100个)或包含复杂子组件:优化后可显著提升拖拽流畅度,且dnd-kit仍在维护、功能更丰富,完全值得投入

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 19:17:05