2019-09-04 06:18:52 weixin_44226217 阅读数 65

最近公司需要做一个可视化的拖拽项目,确定的实现框架为react+react dnd,花了挺大功夫才把它实现,现在把具体实现思路以及实现步骤写出来,方便自己以后翻阅以及给大家提供一个参考

实现思路

先实现拖拽排序,然后实现拖拽增加,最后在增加的时候代理新加入的组件

实现步骤

一、实现拖拽排序

1、首先,新建一个react项目,然后引入reac dnd,指令为

npm install react-dnd react-dnd-html5-backend
yarn add react-dnd react-dnd-html5-backend

安装完毕后,就可以愉快的使用react dnd了
2、创建目录结构:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190904053427510.png)
这里是我临时写的,以后可以根据自己需求改变
介绍一下各个文件的作用
	1、index.js  项目的入口文件
	2、Provider.jsx  拖拽上下文,在react dnd中,只有处于拖拽上下文中的元素才可以被拖动
	3、Library.jsx   拖拽增加的组件,一会用到的时候详细介绍
	4、Box.jsx   拖拽接收的容器,用于接收拖拽组件
	5、Card.js   Box内部的可拖拽的每一项

3、编写拖拽上下文

import HTML5Backend from 'react-dnd-html5-backend'
import { DndProvider } from 'react-dnd'
import { Box } from './Box'
import { Card } from './Card'

定义一个变量CardList,用于遍历出每一个Card,先模拟一下数据

CardList: [
                { id: 1 },
                { id: 2 },
                { id: 3 }
            ]

接下来,我们创建拖拽上下文,根据CardList遍历出一组Card集合

style = {
        backgroundColor: "#eee",
        width: "100%",
        height: "100vh",
        padding: '50px 200px',
        boxSizing: "border-box",
        display: "flex",
        justifyContent: "space-between"
    }
    render() {
        const Cards = this.state.CardList.map((item, index) => {
            return <Card CardItem={item} index={index} key={item.id} moveCard={this.moveCard} />
        })
        return (<div style={this.style}>
            <DndProvider backend={HTML5Backend}>
                <Box>
                    {Cards}
                </Box>
            </DndProvider>
        </div>)
    }

4、编写Box.jsx

import React from 'react';
import { useDrop } from 'react-dnd'

export const Box = ({children }) => {
    const [, drop] = useDrop({
        accept: "mydrop",
    })
    const style = {
        width: "375px",
        height: "100%",
        backgroundColor: "#fff",
        padding: "5px"
    }
    return (<div style={style} ref={drop}>
        {children}
    </div>)
}

这里的accept是作为一个拖拽的标识,只有相同拖拽标识的拖拽才会触发对应的react-dnd方法
5、编写Card.jsx

import React, { useRef } from 'react';
import { useDrag, useDrop } from 'react-dnd'

export const Card = ({ CardItem, index, moveCard }) => {
    const ref = useRef(null)
    const [, drop] = useDrop({
        accept: 'mydrop',
        hover(item, monitor) {
            if (!ref.current) {
                return
            }
            const dragIndex = item.index
            const hoverIndex = index
            if (dragIndex === hoverIndex) {
                return
            }
            const hoverBoundingRect = ref.current.getBoundingClientRect()
            const hoverMiddleY =
                (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
            const clientOffset = monitor.getClientOffset()
            const hoverClientY = clientOffset.y - hoverBoundingRect.top
            if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
                return
            }
            if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
                return
            }
            moveCard(dragIndex, hoverIndex)
            item.index = hoverIndex
        },
    })
    const [, drag] = useDrag({
        item: {
            type: 'mydrop',
            id: CardItem.id,
            index
        }
    })
    drop(drag(ref))
    const style = {
        width: "100%",
        height: "30px",
        backgroundColor: "#eee",
        padding: "4px",
        boxSizing: "border-box",
        border: "1px solid #000",
        marginBottom: "5px"
    }
    return (<div style={{ ...style }} ref={ref}>
        {CardItem.id}
    </div>)
}

这里的思路是,当拖拽的时候,首先判断是不是悬浮在自己身上,如果是,不执行操作,如果不是,则判断鼠标位置是否超过悬浮Card的中点,是的话,改变数组中的两个下标对应的位置,下面是moveCard的具体代码:

moveCard = (dragIndex, hoverIndex) => {
        const dragCard = this.state.CardList[dragIndex];
        this.state.CardList.splice(dragIndex, 1);
        this.state.CardList.splice(hoverIndex, 0, dragCard)
        this.setState({
            CardList: this.state.CardList
        })
    }

注意,这个方法是放在Provider.jsx内的
到这里,拖拽排序已经实现了,接下来实现拖拽添加

二、拖拽添加

1、首先,我们在Provider.jsx中编写几个方法,增加几个变量

constructor(props) {
        super(props)
		this.state = {
            CardList: [
                { id: 1 },
                { id: 2 },
                { id: 3 }
            ],
            hasDrop: false,   //用于使添加方法在一次增加过程中只执行一次
            active: null    //用于标识新加入的Card
        }
 }
addCard = () => {      //增加Card的方法
        if (this.state.hasDrop) return
        let CardItem = this.Random()
        this.setState({
            CardList: [...this.state.CardList, CardItem],
            hasDrop: true,
            active: CardItem.id
        })
    }

Random = () => {    //用于产生一个拥有唯一id的Card,这个方法有待改进
        let CardItem = {
            id: Math.random()
        }
        this.state.CardList.forEach((ele) => {
            if (ele.id === CardItem.id) {
                CardItem = this.Random();
            }
        })
        return CardItem
    }

endDrop = (didDrop) => {      //当结束拖放的时候触发的方法,用于重置hasDrop,以及判断是否可放置
        if (didDrop) {
            this.setState({
                hasDrop: false,
            })
        } else if (this.state.hasDrop) {    //这儿的判断不能省,用于判断拖拽过程中有没有加入新的Card,没有则不执行删除
            this.removeItem(this.state.active)
        }
    }

removeItem = (id) => {   //删除Card的方法,用于如果不被允许放置,则删除掉新加入的方法
        this.state.CardList.map((item, index) => {
            if (item.id === id) {
                this.state.CardList.splice(index, 1);
                this.setState({
                    CardList: this.state.CardList,
                    hasDrop: false
                })
            }
            return 0;
        })
    }

2、接下来,做用于增加的组件的工作:

import React from 'react';
import { useDrag } from 'react-dnd'

function Libarry({endDrop}){
    const [{didDrop}, drag] = useDrag({
        item: {
            type: 'mydrop',
            hasAdd:true  //这儿用于判断是不是区分拖拽增加组件与Card
        },
        end: () => {
            endDrop(didDrop)
        },
        collect:monitor=>({
            didDrop : !!monitor.didDrop()
        })
    })
    const style = {
        display: "block",
        padding: '4px',
        width: "100px",
        height: "24px",
        textAlign: "center",
        backgroundColor: "#FFf",
        border: "1px solid #aaa",
        cursor: "move"
    }
    return (<div style={style} ref={drag}>
        拖动源
    </div>)
}
export default Libarry

3、对Box.jsx做一些调整:

首先,为Box传入增加Card的方法

<Box addCard={this.addCard}>
                    {Cards}
                </Box>

其次,在内部接收,并调用它

export const Box = ({addCard,children }) => {
    const [, drop] = useDrop({
        accept: "mydrop",
        hover: (item) => {
            if(item.hasAdd){
                addCard()
            }
        }
    })
    //...
}

这样,增加就完成了,但是这时候拖拽增加的话,会报错,因为拖拽增加后,触发Card的拖拽方法,但是这时候Library还没有item内还没有Index,现在就需要让Library代理新增加的Card

三、增加代理

首先理一下思路,我们在拖拽Library时会向CardList增加一个Card,新增加的Card的index总是等于CardList.length。所以我们只需要将CardList.length传给Library即可<Library endDrop={this.endDrop} index={this.state.CardList.length} />
现在写Library的内容

import React from 'react';
import { useDrag } from 'react-dnd'

function Libarry({endDrop,index}){
    const [{didDrop}, drag] = useDrag({
        item: {
            type: 'mydrop',
            hasAdd:true,
            index
        },
        end: () => {
            endDrop(didDrop)
        },
        collect:monitor=>({
            didDrop : !!monitor.didDrop()
        })
    })
    const style = {
        display: "block",
        padding: '4px',
        width: "100px",
        height: "24px",
        textAlign: "center",
        backgroundColor: "#FFf",
        border: "1px solid #aaa",
        cursor: "move"
    }
    return (<div style={style} ref={drag}>
        拖动源
    </div>)
}
export default Libarry

这样,就完成的在增加时进行排序的功能,但博主代码水平不高,应该是还可以进行很多优化的,这个放在以后进行吧

2019-08-02 17:32:01 songxueing 阅读数 1657

现在有一个新需求就是需要对一个列表,实现拖拽排序的功能,要实现的效果如下图:

 

可以通过 react-dnd 或者 react-beautiful-dnd 两种方式实现,今天先讲下使用react-dnd是如何实现的,github地址:

https://react-dnd.github.io/react-dnd/docs/api/dnd-provider

1.先安装依赖

npm i react-dnd

npm i react-dnd-html5-backend

2.创建一个 index.js 文件

DndProvider 组件为您的应用程序提供 React-DnD 功能。这必须通过 backend 支柱注入后端,但也可以注入 window 物体。

  • backend:必填。一个React DnD后端。除非您正在编写自定义的,否则您可能希望使用React DnD附带的HTML5后端。
  • context: 可选的。用于配置后端的后端上下文。这取决于后端实现。
  • options: 可选的。用于配置后端的选项对象。这取决于后端实现。
import React from 'react'
import Example from './example'
import { DndProvider } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
	
class App extends React.Component{
    
    /**
    * 获取排序后的新数据回调函数
    */
    handlePreviewList = (previewList) => {
        this.setState({
             previewList
        })
    }
	return (
		<div className="App">
			<DndProvider backend={HTML5Backend}>
				<Example previewList={previewList} 
                    handlePreviewList={this.handlePreviewList}/>
			</DndProvider>
		</div>
	)
}
	
export default App;

3.新建example.js文件

previewList是 index.js组件传入的数据。

handlePreviewList 是保存排序后的新数据。

import React, { useState } from 'react'
import TopicList from './TopicList';

const Container = ({ previewList, handlePreviewList }) => {
    {
        const [topic] = useState(previewList)
        const handleDND = (dragIndex, hoverIndex) => {
            let tmp = previewList[dragIndex] //临时储存文件
            previewList.splice(dragIndex, 1) //移除拖拽项
            previewList.splice(hoverIndex, 0, tmp) //插入放置项
            handlePreviewList(previewList)
        };
        const renderCard = (item, index) => {
            return (
                <TopicList
                    key={item.questionTuid}
                    index={index}
                    id={item.questionTuid}
                    text={item.questionContent}
                    moveCard={handleDND}
                />
            )
        }
        return (
            <div>
                {
                    topic.map((item, i) => renderCard(item, i))
                }
            </div>
        )
    }
}
export default Container

4.新建TopicLis.js文件

useDrag 一个钩子,用于将当前组件用作拖动源。

参数

  • spec规范对象

返回值数组

  • Index 0:包含collect函数中收集的属性的对象。如果collect未定义任何函数,则返回空对象。
  • Index 1:拖动源的连接器功能。这必须附加到DOM的可拖动部分。
  • Index 2:拖动预览的连接器功能。这可以附加到DOM的预览部分。

规范对象成员

  • item:必填。一个简单的JavaScript对象,描述被拖动的数据。这是关于拖动源的放置目标可用的唯一信息,因此选择他们需要知道的最小数据非常重要。你可能想在这里放一个复杂的引用,但是你应该尽量避免这样做,因为它耦合了拖动源和放下目标。返回类似于{ type, id }此方法的东西是个好主意。

    item.type必须设置,它必须是字符串,ES6符号。只有为相同类型注册的放置目标才会对此项目做出反应。阅读概述以了解有关项目和类型的更多信息。

  • previewOptions: 可选的。描述拖动预览选项的纯JavaScript对象。

  • options: 可选的。一个普通的对象。如果组件的某些道具不是标量(即,不是原始值或函数),则arePropsEqual(props, otherProps)options对象内指定自定义函数可以提高性能。除非您遇到性能问题,否则不要担心。

  • begin(monitor): 可选的。拖动操作开始时触发。不需要返回任何内容,但如果返回一个对象,它将覆盖item规范的默认属性。

  • end(item, monitor): 可选的。当拖动停止时,end被调用。对于每个begin呼叫,end保证相应的呼叫。您可以调用monitor.didDrop()以检查丢弃是否由兼容的放置目标处理。如果它被处理,并且放置目标通过从其方法返回普通对象来指定放置结果drop(),则它将可用作monitor.getDropResult()。此方法是触发Flux动作的好地方。注意:如果在拖动时卸载组件,则component参数设置为null

  • canDrag(monitor): 可选的。用它来指定当前是否允许拖动。如果您想要始终允许它,只需省略此方法即可。如果您想基于某些谓词禁用拖动,则指定它很方便props注意:您可能无法调用monitor.canDrag()此方法。

  • isDragging(monitor): 可选的。默认情况下,仅启动拖动操作的拖动源被视为拖动。您可以通过定义自定义isDragging方法来覆盖此行为。它可能会返回类似的东西props.id === monitor.getItem().id。如果在拖动过程中可以卸载原始组件并在以后使用其他父级“复活”,则执行此操作。例如,当在卡片板中的列表中移动卡时,您希望它保留拖动的外观 - 即使在技术上,组件也会被卸载,并且每次将其移动到另一个列表时都会安装另一个组件。注意:您可能无法调用monitor.isDragging()此方法。

  • collect: 可选的。收集功能。它应该返回一个普通的道具对象返回注入你的组件。它接收两个参数,monitorprops。阅读概述,了解监视器和收集功能的介绍。请参阅下一节中详细描述的收集功能。

 

useDrop 一个钩子,用于将当前组件用作放置目标。

参数

  • spec规范对象

返回值数组

  • Index 0:包含collect函数中收集的属性的对象。如果collect未定义任何函数,则返回空对象。
  • Index 1:放置目标的连接器功能。这必须附加到DOM的drop-target部分。

规范对象成员

  • accept:必填。一个字符串,一个ES6符号,一个数组或一个返回给定组件的函数的函数props。此放置目标仅对指定类型的拖动源生成的项目作出反应。

  • options: 可选的。一个普通的对象。如果组件的某些道具不是标量(即,不是原始值或函数),则arePropsEqual(props, otherProps)options对象内指定自定义函数可以提高性能。除非您遇到性能问题,否则不要担心。

  • drop(item, monitor): 可选的。在目标上放置兼容项目时调用。您可以返回undefined或普通对象。如果返回一个对象,它将成为放置结果,并且可以在其endDrag方法中作为拖动源使用monitor.getDropResult()。如果您希望根据接收到丢弃的目标执行不同的操作,这非常有用。如果您有嵌套的放置目标,则可以drop通过检查monitor.didDrop()和测试是否已经处理了嵌套目标monitor.getDropResult()。此方法和源endDrag方法都是触发Flux操作的好地方。如果canDrop()已定义并返回,则不会调用此方法false

  • hover(item, monitor): 可选的。当项目悬停在组件上时调用。您可以检查monitor.isOver({ shallow: true })以测试悬停是发生在当前目标上还是嵌套上。与drop()此不同,即使canDrop()定义并返回,也会调用此方法false。您可以检查monitor.canDrop()以测试是否是这种情况。

  • canDrop(item, monitor): 可选的。使用它来指定放置目标是否能够接受该项目。如果您想要始终允许它,只需省略此方法即可。如果你想基于某个谓词over props或者禁用删除,那么指定它是很方便的monitor.getItem()注意:您可能无法调用monitor.canDrop()此方法。

  • collect: 可选的。收集功能。它应该返回一个普通的道具对象返回注入你的组件。它接收两个参数,monitorprops,了解监视器和收集功能的介绍。请参阅下一节中详细描述的收集功能。

import React, { useRef } from 'react'
import { useDrag, useDrop } from 'react-dnd'
import ItemTypes from './ItemTypes';
const style = {
  padding: '0.5rem 1rem',
  marginBottom: '.5rem',
  backgroundColor: 'white',
  cursor: 'move',
}
const TopicList = ({ id, text, index, moveCard }) => {
  const ref = useRef(null)
  const [, drop] = useDrop({
    //定义拖拽的类型
    accept: ItemTypes.TOPIC,    
    hover(item, monitor) {
      //异常处理判断
      if (!ref.current) { 
        return
      }
      //拖拽目标的Index
      const dragIndex = item.index;
      //放置目标Index
      const hoverIndex = index; 
      // 如果拖拽目标和放置目标相同的话,停止执行
      if (dragIndex === hoverIndex) { 
        return
      }
      //如果不做以下处理,则卡片移动到另一个卡片上就会进行交换,下方处理使得卡片能够在跨过中心线后进行交换.
      //获取卡片的边框矩形
      const hoverBoundingRect = ref.current.getBoundingClientRect();    
      //获取X轴中点
      const hoverMiddleY =
        (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; 
      //获取拖拽目标偏移量
      const clientOffset = monitor.getClientOffset();   
      const hoverClientY = clientOffset.y - hoverBoundingRect.top
      // 从上往下放置
      if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {  
        return
      }
      // 从下往上放置
      if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {  
        return
      }
      moveCard(dragIndex, hoverIndex);  //调用方法完成交换
      item.index = hoverIndex;  //重新赋值index,否则会出现无限交换情况
    },
  })
  const [{ isDragging }, drag] = useDrag({
    item: { type: ItemTypes.TOPIC, id, index },
    collect: monitor => ({
      isDragging: monitor.isDragging(),
    }),
  })
  const opacity = isDragging ? 0 : 1
  drag(drop(ref))
  return (
    <div ref={ref} style={{ ...style, opacity }}>
      <span style={{ float: 'left' }}>{index + 1}.</span>
      <div className='stem' dangerouslySetInnerHTML={{ __html: text }}></div>
    </div>
  )
}
export default TopicList

5.新建 ItemTypes.js

export default {
  TOPIC: 'topic'
}

注意:react的版本需要是react16的新版本,否则会报如下图所示的错误,具体兼容到几,未测试,但是之前的react 16.4.2是实现不了当前功能的,所以在开发前请确认react版本

2019-11-28 16:16:25 qq_35164944 阅读数 9

react-dnd是什么?

直截了当的说,react-dnd是一个高阶组件,可以用来实现节点可拖拽以及可释放的功能,整个流程清晰明了,这是一篇从零开始的使用记录,点击查看官网

核心组成

既然要实现可拖拽,首先我们自然需要知道我们要拖动什么,放在哪,我把这两个称为拖动源及放置目标区。

包裹组件生成拖动放置区域

DndProvider/DragDropContext前面是元素组件,后一个同样是高阶组件,使用过程中之前用@DragDropContext(HTML5Backend)但是页面没有反应,具体原因可能跟版本有关系,所以改为DndProvider。
注意:必须在当前组件包裹拖动源及目标区才成功使用了react-dnd

import React, { Component } from 'react';
import HTML5Backend from 'react-dnd-html5-backend';
import { DndProvider } from 'react-dnd';

export default class Container extends Component {
  render(){
	            <DndProvider backend={HTML5Backend}>
	/**内容必须包含可拖动元素及拖动区域**/
	
</DndProvider>

}
}

生成拖动源

dragSource是一个高阶组件,可以用来包裹你想要拖拽的节点组件让其变成可拖动:@DragSource(DND_TYPE, spec, collect)这个方法带有三个参数:

  • DND_TYPE:这是约定拖动源和目的地的一个字符串,只有两边相同的type拖动源才能被放置在目标区域;

  • spec:这是一个包含各种方法的对象

    • beginDrag(props, monitor, component):开始拖拽时触发的方法,返回一个对象,数据可被monitor.getItem()获取。props可以是从父组件传过来的参数,monitor即当前拖动的一个实例,component表示当前组件,可以从中获取组件数据。
    • endDrag(props, monitor, component): 结束拖拽时触发,一般可以不使用,可选。
    • canDrag(props, monitor): 判断当前是否允许拖动,默认允许,可以不设置,可选。
    • isDragging(props, monitor): 官方解释只有启动拖动操作的拖动源才被视为拖动,不设置即可,可选。
  • collect: 将事件处理程序连接到组件中的某个节点, 包含两个参数connect,monitor

// 下拉列表的拖动组件

import React, { Component } from "react";
import { DragSource } from 'react-dnd';
import { DND_TYPE, DATA_TYPE, UTIL_TYPE } from './DnDType'

const spec = {
    beginDrag(props, monitor, component) {
       return {
            type: UTIL_TYPE, // 用于判断在画布渲染的时候是什么组件
            width: 72,
            height: 72,
            name: props.name,
        }
    }
}
function collect(connect, monitor) {
    return {
        connectDragSource: connect.dragSource(),
        isDragging: monitor.isDragging()
    }
}
@DragSource(DND_TYPE, spec, collect)
export default class TextDrag extends Component {
    constructor(props) {
        super(props)
    }

    render() {
        const { connectDragSource, isDragging, name } = this.props;
        return (
            <div ref={connectDragSource} className={'menu-list-item'}>{name}</div>
        )
    }
}

规定放置区域DropTarget

规定当前区域可以允许放置/拖动@DropTarget(DND_TYPE, dropSpec, collect)与拖动源三个参数类似:

  • DND_TYPE: 与拖动源的type相匹配,只有一样才可以放置
  • dropSpec: 包含三个方法都是可选,所使用参数与拖动源相同
    • drop(props, monitor, component):当前拖动放下时触发,可以返回对象会在endDrag中获取,一般在这里表示拖动结束进行下一步的其他操作
    • hover(props, monitor, component):悬停时调用,没有使用。
    • canDrop(props, monitor): 同拖动源一致,判断能否在当前放置元素
      collect: 与拖动源类似,用于连接节点。
import React, { Component } from "react";
import { DropTarget } from 'react-dnd';
import { connect } from "dva";
import { DND_TYPE } from "./DnDType";

const dropSpec = {
    drop(props, monitor, component) {
        /**
         * 获取index页面传递过来的回调方法
         */
        const { props: { setNode } } = component
        let info = {};
        info.position = monitor.getClientOffset();
        info.info = monitor.getItem();
        setNode(info);
    }
};

function collect(connect, monitor) {
    return {
        connectDropTarget: connect.dropTarget(),
        isOver: monitor.isOver(),
        canDrop: monitor.canDrop()
    };
}

DropTarget(DND_TYPE, dropSpec, collect)
export default class MainContainer extends Component {
    constructor(props) {
        super(props)
    }
    render() {
        const { connectDropTarget, isOver, canDrop, list } = this.props;
        const isActive = canDrop && isOver;
        return connectDropTarget(
            <div id="target">
                <div id="con">
                    {this.props.list}
                </div>
             </div>
        )
        }
        }

2017-02-21 15:45:29 ISaiSai 阅读数 3322

参考文档: https://react-dnd.github.io/react-dnd/examples-sortable-simple.html

核心理解:
1.ItemTypes.CARD, cardTarget, connect
第一个参数是类型,第二个参数是 事件注册,第三个参数是个函数,返回值要注入到组件总的内容

2.beginDrag 的返回值,可以理解为原生的(dataTransfer.setData),设置拖动的数据,传递给hover中使用,(monitor.getItem().index)

3.使用了ES7 的decorator

“`
import React, { Component, PropTypes } from ‘react’;
import { findDOMNode } from ‘react-dom’;
import { DragSource, DropTarget } from ‘react-dnd’;
import ItemTypes from ‘./ItemTypes’;

const style = {
border: ‘1px dashed gray’,
padding: ‘0.5rem 1rem’,
marginBottom: ‘.5rem’,
backgroundColor: ‘white’,
cursor: ‘move’,
};

// dnd 生命的对象 拖动源
const cardSource = {
// 开始拖动
beginDrag(props) {
//monitor.getItem() 的对象
return {
id: props.id,
index: props.index,
};
},
};

// 拖动目标 事件
const cardTarget = {
// hover
// props 当前组件的属性
// monitor 拖动中的对象
// componet 当前组件

hover(props, monitor, component) {

    // 当前拖动的index
    const dragIndex = monitor.getItem().index;

    // 当前hover 的index
    const hoverIndex = props.index;

    // Don't replace items with themselves
    if (dragIndex === hoverIndex) {
        return;
    }

    // Determine rectangle on screen
    const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();

    // Get vertical middle
    const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;

    // Determine mouse position
    const clientOffset = monitor.getClientOffset();

    // Get pixels to the top
    const hoverClientY = clientOffset.y - hoverBoundingRect.top;

    // Only perform the move when the mouse has crossed half of the items height
    // When dragging downwards, only move when the cursor is below 50%
    // When dragging upwards, only move when the cursor is above 50%

    // Dragging downwards
    if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
        return;
    }

    // Dragging upwards
    if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
        return;
    }

    // Time to actually perform the action
    props.moveCard(dragIndex, hoverIndex);

    // Note: we're mutating the monitor item here!
    // Generally it's better to avoid mutations,
    // but it's good here for the sake of performance
    // to avoid expensive index searches.
    // 新的位置
    monitor.getItem().index = hoverIndex;
},

};

// 声明放下
@DropTarget(ItemTypes.CARD, cardTarget, connect => ({
connectDropTarget: connect.dropTarget(),
}))
// 声明拖动 第三个参数 返回值会注入到对象中
@DragSource(ItemTypes.CARD, cardSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
}))
export default
class Card extends Component {
static propTypes = {
connectDragSource: PropTypes.func.isRequired,
connectDropTarget: PropTypes.func.isRequired,
index: PropTypes.number.isRequired,
isDragging: PropTypes.bool.isRequired,
id: PropTypes.any.isRequired,
text: PropTypes.string.isRequired,
moveCard: PropTypes.func.isRequired,
};

render() {

    //   isDragging, connectDragSource, connectDropTarget  是 注入的
    // text 是父亲传递进来的
    const { text, isDragging, connectDragSource, connectDropTarget } = this.props;


    // 是否是isDragging
    const opacity = isDragging ? 0 : 1;

    return connectDragSource(connectDropTarget(
        <div style={{ ...style, opacity }}>
            {text}
        </div>,
    ));
}

}

“`

2019-12-13 17:58:44 qq_32654773 阅读数 29
import React, { Component } from 'react'
import { DndProvider, DragSource, DropTarget } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'

// 被拖拽的项
const source = {
  // 卡片开始被拖拽时触发
  beginDrag(props) {
    // console.log('开始被拖拽', props.index)
    return {
      index: props.index
    }
  },
  // 卡片结束拖拽时触发
  endDrag(props) {
    // console.log('结束拖拽', props.index)
  }
}

// 拖拽目的项
const target = {
  props(props, monitor) {
    const dragIndex = monitor.getItem().index         // 被拖拽前的位置
    const hoverIndex = props.index                    // 拖拽后的位置
    // 拖拽位置不变
    if (dragIndex === hoverIndex) {
      return;
    }
	props.handleMove(dragIndex, hoverIndex)
  }
}

const DraggbleDiv = props => {
  const { connectDragSource, connectDropTarget, item, index } = props
  return (
    connectDragSource(connectDropTarget(
      <div key={index}>{item.title}</div>
    ))
  )
}

const DraggbleBox = (
  DragSource('test', source, (connect, monitor) => ({
    connectDragSource: connect.dragSource()
  })))(DropTarget('test', target, (connect, monitor) => ({
    connectDropTarget: connect.dropTarget()
  }))(DraggbleDiv)
)

class demo extends Component {

	constructor(){
	    super()
	    this.state = {
	      list: [{
	        title: '更新内容'
	      },{
	        title: '更新时间'
	      },{
	        title: '学习提醒'
	      }]
	    }
	
	    this.handleMove = this.handleMove.bind(this)
	
	  }
	
	handleMove(dragIndex, hoverIndex){
	    const { list } = this.state
	    const temp = list[dragIndex]
	    if(dragIndex > hoverIndex){
	      for(let i = dragIndex; i > hoverIndex; i--){
	        list[i] = list[i-1]
	      }
	    }else{
	      for(let i = dragIndex; i < hoverIndex; i++){
	        list[i] = list[i+1]
	      }
	    }
	    list[hoverIndex] = temp
	    this.setState({ list })
	  }
	
	render() {
	
	    const { list } = this.state
	
	    return (

		 <div>
		     <DndProvider backend={HTML5Backend}>
		       {
		         list.map((item, index) => (
		           <DraggbleBox 
		             item={item}
		             index={index}
		             key={index}
		             handleMove={this.handleMove}
		           />
		         ))
		       }
		     </DndProvider>
		   </div>
	
	    )
	  }
	
}

export default demo

没有更多推荐了,返回首页