夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 核心概念

  • 高级指引

  • Hook

  • 案例演示

  • Router

  • Redux

  • Jotai

  • Mobx

  • 其他

    • React使用经验
    • React的重新渲染详解
    • forwardRef和useImperativeHandle
    • SWR去除重复请求
    • React封装拖拽组件
      • 安装依赖
      • 使用
      • 容器组件
      • 拖拽组件
    • React实现分页打印
  • 《React》笔记
  • 其他
夜猫子
2023-05-30
目录

React封装拖拽组件

# 安装依赖

yarn add react-dnd react-dnd-html5-backend // 拖拽库
yarn add immutability-helper // 官方推荐的不可变数据更改库
1
2

# 使用

import { memo, useCallback, useEffect, useState } from "react";
import DndContainer from "../../../components/DndContainer";
import DndDragItem from "../../../components/DndDragItem";
import type { onDragEndCallbackData } from "../../../components/DndContainer";
import update from "immutability-helper";

const Banner = () => {
  const [cards, setCards] = useState([
    {
      id: 1,
      text: "Write a cool JS library",
    },
    {
      id: 2,
      text: "Make it generic enough",
    },
    {
      id: 3,
      text: "Write README",
    },
    {
      id: 4,
      text: "Create some examples",
    },
    {
      id: 5,
      text: "Spam in Twitter and IRC to promote it (note that this element is taller than the others)",
    },
    {
      id: 6,
      text: "???",
    },
    {
      id: 7,
      text: "PROFIT",
    },
  ]);

  const onDragEnd = (data: onDragEndCallbackData) => {
    const { item, dragIndex, hoverIndex } = data;
    setCards(
      update(cards, {
        $splice: [
          [dragIndex, 1],
          [hoverIndex, 0, item],
        ],
      })
    );
  };

  const renderTableRow = useCallback((card: { id: number; text: string }, index: number) => {
    return (
      <DndDragItem key={card.id} id={`${card.id}`}>
          <div>{text}</div>
      </DndDragItem>
    );
  }, []);

  return (
    <DndContainer data={cards} onDragEnd={onDragEnd}>
        {cards.map((item, index) => renderTableRow(item, index))}
    </DndContainer>
  );
};

export default Banner;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

# 容器组件

import { createContext, useContext, useState, useCallback } from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";

export const formatArrByIndex = (arr: any[], dragIndex: number, hoverIndex: number) => {
  const _arr = [...arr];
  _arr.splice(dragIndex, 1, ..._arr.splice(hoverIndex, 1, _arr[dragIndex]));
  return _arr;
};

export declare type onDragEndCallbackData = {
  data: any;
  item: any;
  dragId: string;
  dragIndex: number;
  hoverIndex: number;
};

type Props = {
  children: React.ReactNode;
  data: any[];
  onDragEnd?: (data: onDragEndCallbackData) => void;
};

interface DndProviderContext {
  moveitem: (id: string, atIndex: number) => void;
  finditem: (id: string) => any;
}

const DndProviderContext = createContext<DndProviderContext | null>(null);

export const useDndProviderContext = () => {
  const context = useContext(DndProviderContext);
  if (!context) throw new Error("useDndProviderContext must be use inside DndProviderContext");

  return context;
};

const DndContainer = ({ children, data, onDragEnd }: Props) => {
  const dataSourse = data;
  const [items, setitems] = useState(data);

  const finditem = useCallback(
    (id: string) => {
      const item = items.filter((c) => `${c.id}` === id)[0];
      return {
        item,
        index: items.indexOf(item),
      };
    },
    [items]
  );

  const moveitem = useCallback(
    (id: string, atIndex: number) => {
      const { item, index } = finditem(id);
      const data = formatArrByIndex(dataSourse, index, atIndex);
      onDragEnd && onDragEnd({ data, item, dragId: id, dragIndex: index, hoverIndex: atIndex });
    },
    [finditem, items, setitems]
  );

  return (
    <DndProvider backend={HTML5Backend}>
      <DndProviderContext.Provider value={{ finditem, moveitem }}>
        {children}
      </DndProviderContext.Provider>
    </DndProvider>
  );
};

export default DndContainer;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

# 拖拽组件

import type { FC } from "react";
import { useRef, memo } from "react";
import { useDrag, useDrop } from "react-dnd";
import { useDndProviderContext } from "./DndContainer";

import { Box } from "@mui/material";

type Props = { children: React.ReactNode; id: string };

interface mainItem {
  id: string;
  originalIndex: number;
}

const ItemTypes = {
  ITEM: "TableRow",
};

const DndDragItem: FC<Props> = memo(({ children, id }) => {
  const ref = useRef<HTMLDivElement>(null);

  const { finditem, moveitem } = useDndProviderContext();

  const originalIndex = finditem(id).index;
  const [{ isDragging }, drag] = useDrag(
    () => ({
      type: ItemTypes.ITEM,
      item: { id, originalIndex },
      collect: (monitor) => ({
        isDragging: monitor.isDragging(),
      }),
      end: (item, monitor) => {
        const { id: droppedId, originalIndex } = item;
        const didDrop = monitor.didDrop();
        if (!didDrop) {
          // 拖放失败时复原数据
          moveitem(droppedId, originalIndex);
        }
      },
    }),
    [id, originalIndex, moveitem]
  );

  const [, drop] = useDrop(
    () => ({
      accept: ItemTypes.ITEM,
      hover({ id: draggedId }: mainItem) {
        if (draggedId !== id) {
          const { index: overIndex } = finditem(id);
          moveitem(draggedId, overIndex);
        }
      },
    }),
    [finditem, moveitem]
  );

  const opacity = isDragging ? 0 : 1;

  drag(drop(ref));
  return (
    <Box
      sx={{
        opacity: opacity,
      }}
      ref={ref}
    >
      {children}
    </Box>
  );
});

export default DndDragItem;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
编辑 (opens new window)
上次更新: 2024/12/19 17:25:54
SWR去除重复请求
React实现分页打印

← SWR去除重复请求 React实现分页打印→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式