react-dnd拖拽组件

标题 react-dnd组件结合 Hook实现嵌套拖拽、卡片排序、卡片删除等功能

<div className="main">
    <ReducerContext>
         <DndProvider backend={
     HTML5Backend}>
             <Left />
             <Right />
         </DndProvider>
     </ReducerContext>
</div> 
import React, {
      useContext } from 'react';
import {
      Menu } from 'cloud-react';
import SourceBox from './SourceBox';
import {
      Context } from '../reducer'
const {
      MenuItem } = Menu;

const types = ['View', 'Text', 'Button', 'Button3'];

export default function Left() {
     
    const {
      dispatch, state } = useContext(Context);
    return (
        <Menu>
            {
     
                types.map((type, index) => {
     
                    return (
                        <SourceBox dispatch={
     dispatch} name={
     type} key={
     index}>
                            <MenuItem>{
     type}</MenuItem>
                        </SourceBox>
                    )
                })
            }
        </Menu>
    )
}
import React, {
      Component } from 'react';
import {
      findDOMNode } from 'react-dom';
import {
      observer } from 'mobx-react';
import {
      DragSource, DropTarget } from 'react-dnd';
import './index.css'
import List from './list';
import Components from './components';

const source = {
     
	/**
	 * 拖拽前为组件增加一些属性
	 * @param {*} props
	 */
	beginDrag(props) {
     
		const {
      parentId, item } = props;
		// console.log(props);
		const {
      id, type, childrens } = item;
		return {
     
			id,
			parentId,
			type,
			items: childrens
		};
	},

	/**
	 * 限制组件是否可拖拽
	 * @param {*} props
	 */
	canDrag(props) {
     
		if (props.item.id === 0) return false;
		return true;
	},

	/**
	 * 当前组件是否处于拖拽中
	 * @param {*} props
	 * @param {*} monitor
	 */
	isDragging(props, monitor) {
     
		return props.item.id === monitor.getItem().id;
	},

	/**
	 * 
	 * @param {*} props
	 * @param {*} monitor
	 */
	endDrag(props, monitor) {
     
		const result = monitor.getDropResult();
		if (result.dragItem) {
     
			const {
      dragItem, overItem } = result;
			const {
      draggedId, dragParentId } = dragItem;
			const {
      overId, overParentId } = overItem;
			if (result.moveShift) {
     
				//同级拖拽
				props.dispatch({
     
					type: 'moveShift',
					payload: {
      dragItem, overItem }
				});

			} else {
     
				//view与box之间拖拽
				props.dispatch({
     
					type: 'moveItem',
					payload: {
      dragItem, overItem }
				});
			}

		}
	}
};

function sourceCollect(connect, monitor) {
     
	return {
     
		connectDragSource: connect.dragSource(),
		connectDragPreview: connect.dragPreview(),
		isDragging: monitor.isDragging()
	};
}

const target = {
     
	/**
	 * 是否可以将拖拽的元素放置
	 * @param {*} props
	 * @param {*} monitor
	 */
	canDrop(props, monitor) {
     
		// 在此处可以获取到拖拽的组件类型,从而增加一些是否可以放置的条件
		return true;
	},
	/**
	 * 使用drop而未使用hover是不想一直更改数据结构
	 * @param {*} props
	 * @param {*} monitor
	 */
	drop(props, monitor, component) {
     
		const didDrop = monitor.didDrop();
		if (didDrop) {
     
			return undefined;
		}
		const {
      id: draggedId, parentId: dragParentId } = monitor.getItem();
		const {
      parentId: overParentId } = props;
		const {
      id: overId, type: targetType } = props.item;
		console.log("-------------drop-props", props);
		/* 
			monitor.getItem():    {type: "View"}    来自beginDrag return的
			props :  {parentId: null, item: Proxy, move: ƒ}   来自父级属性
		*/
		console.log("draggedId:", draggedId, "dragParentId:", dragParentId); //来自beginDrag return的 ----即 拖拽节点的属性
		console.log("overParentId:", overParentId, "overId:", overId); //来自父级属性			----即 被接收容器的属性

		if (draggedId) {
      //右侧内部拖拽
			if (draggedId === overId || draggedId === overParentId || dragParentId === overId) return undefined;
			if (dragParentId === overId) return undefined;
			if (dragParentId == overParentId && targetType != "View") {
      //同层切换顺序
				const hoverBoundingRect = (findDOMNode(component)).getBoundingClientRect();
				// 获取中点垂直坐标
				const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;//放置目标节点中心点
				// 确定鼠标位置
				const clientOffset = monitor.getClientOffset();
				// 获取距顶部距离
				const hoverClientY = (clientOffset).y - hoverBoundingRect.top;  //鼠标距放置节点top的距离

				console.log(hoverClientY, hoverMiddleY);
				if (hoverClientY != hoverMiddleY && dragParentId == overParentId) {
     //同级拖拽
					return {
     
						dragItem: {
      draggedId, dragParentId },
						overItem: {
      overId, overParentId },
						moveShift: 1
					}
				}
			} else {
     
				//非切换顺序
				return {
     
					dragItem: {
      draggedId, dragParentId },
					overItem: {
      overId, overParentId }
				};

			}
		}
		// 左侧拖右侧
		return {
      id: overId, overParentId: overParentId, targetType: targetType };  //来自上一级 data数据里的item的id-----即被接收容器的id
	}
};

function targetCollect(connect, monitor) {
     
	return {
     
		connectDropTarget: connect.dropTarget(),
		isOver: monitor.isOver({
      shallow: true }),
		canDrop: monitor.canDrop()
	};
}
class Item extends Component {
     
	constructor(props) {
     
		super(props)
		this.state = {
     
			hover: false
		}
	}

	render() {
     
		const {
      connectDropTarget, connectDragSource, canDrop, isOver, item, dispatch } = this.props;
		const {
      id, type, childrens } = item;
		const CurrentComponet = Components[type];
		const classes = (canDrop && isOver) ? 'activeHover' : '';
		return (
			<div >
				<CurrentComponet
					id={
     id}
					type={
     type}
					className={
     `item ${
       classes}`}
					ref={
     instance => {
     
						connectDragSource(findDOMNode(instance));
						connectDropTarget(findDOMNode(instance));
					}}>
					<List dispatch={
     dispatch} parentId={
     id} items={
     childrens} />
				</CurrentComponet>
			</div>
		);
	}
}

export default DropTarget('ITEM', target, targetCollect)(DragSource('ITEM', source, sourceCollect)(Item));


reducer.js 数据处理


import React, {
      createContext, useReducer } from 'react'

const initState = [
    {
     
        id: 0,
        type: 'Box',
        childrens: []
    }
];
function findItem(dataList, id) {
     
    let result = null;
    dataList.forEach(item => {
     
        const loop = data => {
     
            if (data.id === id) {
     
                result = data;
                return result;
            }

            const childs = data.childrens;
            if (childs) {
     
                for (let i = 0; i < childs.length; i += 1) {
     
                    loop(childs[i]);
                }
            }
        };

        loop(item);
    });

    return result;
}
const removeNode = (state, removeId, parentId) => {
     
    const item = findItem(state, parentId);
    const index = item.childrens.findIndex(child => child.id === removeId);
    item.childrens.splice(index, 1);
}
function reducer(state, action) {
     
    switch (action.type) {
     
        case 'additem':
            const {
      targetId, type, over_ParentId, targetType } = action.payload;
            // console.log(type, targetId, over_ParentId, targetType);

            let item;
            if (targetType == "Box" || (targetType == "View" && type !== targetType)) {
     
                //push 到box

                item = findItem(state, targetId);

            } else {
     
                item = findItem(state, over_ParentId);
                if (item.type == "View" && type == "View") {
      //阻止嵌套View组件
                    return state
                }
            }

            const obj = {
     
                id: Math.ceil(Math.random() * 10000),
                type
            };
            if (type == "View") {
     
                obj.childrens = []
            }
            item.childrens.push(obj)
            console.log(state);
            // 进行深拷贝 分配新数组
            const a = JSON.parse(JSON.stringify(state))
            return a
        case 'moveItem':
            var {
      dragItem, overItem } = action.payload;
            var {
      draggedId, dragParentId } = dragItem;
            var {
      overId, overParentId } = overItem;

            const Item = {
      ...findItem(state, draggedId) };
            const target = findItem(state, overId);
            const overParentITem = findItem(state, overParentId);
            const dragParentIdITem = findItem(state, dragParentId);
            // console.log(JSON.stringify(Item));
            // console.log(JSON.stringify(target));
            // console.log(overParentITem);
            // console.log(dragParentIdITem);
            if (Item.type == "View") {
     
                return state
            }
            removeNode(state, draggedId, dragParentId);
            //push到当前view/BOx层
            if (target.type == "View" || target.type == "Box") {
     
                target.childrens.push(Item)
            } else {
     
                overParentITem.childrens.push(Item)
            }
            const b = JSON.parse(JSON.stringify(state))
            return b
        case 'moveShift':
            console.log(state);
            var {
      dragItem, overItem } = action.payload;
            var {
      draggedId, dragParentId } = dragItem;
            var {
      overId, overParentId } = overItem;
            const moveShiftItem1 = {
      ...findItem(state, draggedId) };
            const moveShiftItem2 = {
      ...findItem(state, overId) };
            const moveShiftAtrr = {
      ...findItem(state, dragParentId) }.childrens;
            const moveShiftItem1Index = moveShiftAtrr.findIndex(v => v.id === moveShiftItem1.id);
            const moveShiftItem2Index = moveShiftAtrr.findIndex(v => v.id === moveShiftItem2.id);
            console.log(moveShiftItem1Index, moveShiftItem2Index);
            const newM = moveShiftAtrr.splice(moveShiftItem1Index, 1)[0];
            // moveShiftAtrr[moveShiftItem2Index] = moveShiftAtrr.splice(moveShiftItem1Index, 1, moveShiftAtrr[moveShiftItem2Index])[0];
            moveShiftAtrr.splice(moveShiftItem2Index, 0, newM);

            return JSON.parse(JSON.stringify(state))
        case 'remove':
            return {
     
                ...state,
            }
        default:
            return state;
    }
}

export const Context = createContext({
     });

export const ReducerContext = (props) => {
     
    //利用useReducer,将当前reducer中需要处理的方法进行导出,useReducer的第一个参数表示要处理的相关逻辑,第二个参数表示初始值
    const [state, dispatch] = useReducer(reducer, initState)
    return (
        //在这里我们使用了useContext进行了状态的共享
        <Context.Provider value={
     {
      state, dispatch }}>
            {
     props.children}
        </Context.Provider>
    )
}

具体代码可参考地址https://github.com/xiahaox/react-dnd-Hook.git

你可能感兴趣的:(JS,javascript)