简介
在公司初学react,其中一个要求让我实现拖拽排序的功能,完成之后记录一下实现方法,采用antd和reactDND来实现这个功能。
一、环境搭建
首先,使用 create-react-app
脚手架创建一个最基本的react项目。
npm install -g create-react-app
create-react-app my-app
cd my-app
OK,构建好了react项目,然后我们引入antd
,和react-dnd
$ yarn add antd
$ yarn add react-dnd
$ yarn add react-dnd-html5-backend
引用完antd后可以按照antd官网上的方法完成按需加载。
二、功能实现
我们先使用antd写出一个简单的卡片列表,修改项目目录的APP.js和App.css文件,新建一个文件CardItem.js
//App.js
import React, { Component } from 'react';
import CardItem from './CardItem'
import './App.css';
const CardList = [{ //定义卡片内容
title:"first Card",
id:1,
content:"this is first Card"
},{
title:"second Card",
id:2,
content:"this is second Card"
},{
title:"Third Card",
id:3,
content:"this is Third Card"
}
];
class App extends Component {
state = {
CardList
};
render() {
return (
<div className='card'>
{CardList.map((item,index) => {
return(
<CardItem //向次级界面传递参数
key={item.id}
title={item.title}
content={item.content}
index={index}
/>
)
})}
</div>
);
}
}
export default App;
//App.css
.card{
display: flex;
margin: 50px;
}
.card div{
margin-right: 20px;
}
//CardItem.js
import React, { Component } from 'react';
import {Card} from 'antd'
class CardItem extends Component{
render(){
return(
<div>
<Card
title={this.props.title}
style={{ width: 300 }}
>
<p>{this.props.content}</p>
</Card>
</div>
)
}
}
export default CardItem
好了,卡片编写完成了,现在运行一下我们的项目,看一下效果
$ npm start or yarn start
OK,编写完成,我们现在要做的就是使用react-dnd
完成卡片的拖拽排序,使得firstCard,secondCard,thirdCard可以随意的交换。
react-dnd
中提供了DragDropContext
,DragSource
,DropTarget
3种API;
-
DragDropContext 用于包装拖拽根组件,
DragSource
和DropTarget
都需要包裹在DragDropContex
内 - DropTarget 用于包装你需要拖动的组件,使组件能够被拖拽
- DragSource 用于包装接收拖拽元素的组件,使组件能够放置
理解了这些API的作用,一个卡片排序的构建思路大体就浮现出来了,怎么样实现一个卡片排序,其实很简单,就是把卡片列表中的每一个卡片都设置为DropTarget
和DragSource
,最后在拖拽结束的时候进行卡片之间的重排序,完成这一功能的实现。下面我们就来一步一步的实现它。
首先设定DragDropContext
,在App.js
中引入 react-dnd
和react-dnd-html5-backend
(先npm install
这个插件)
//App.js
import React, { Component } from 'react';
import CardItem from './CardItem'
+ import {DragDropContext} from 'react-dnd'
+ import HTML5Backend from 'react-dnd-html5-backend'
import './App.css';
/*..
..*/
- export default App;
+ export default DragDropContext(HTML5Backend)(App);
好了,现在被App.js
所包裹的子组件都可以使用DropTarget
和DragSource
了,我们现在在子组件CardItem
中设定react-dnd
使得卡片现在能够有拖动的效果。
//CardItem.js
import React, { Component } from 'react';
import {Card} from 'antd'
+ import { //引入react-dnd
DragSource,
DropTarget,
} from 'react-dnd'
const Types = { // 设定类型,只有DragSource和DropTarget的类型相同时,才能完成拖拽和放置
CARD: 'CARD'
};
//DragSource相关设定
const CardSource = { //设定DragSource的拖拽事件方法
beginDrag(props,monitor,component){ //拖拽开始时触发的事件,必须,返回props相关对象
return {
index:props.index
}
},
endDrag(props, monitor, component){
//拖拽结束时的事件,可选
},
canDrag(props, monitor){
//是否可以拖拽的事件。可选
},
isDragging(props, monitor){
// 拖拽时触发的事件,可选
}
};
function collect(connect,monitor) { //通过这个函数可以通过this.props获取这个函数所返回的所有属性
return{
connectDragSource:connect.dragSource(),
isDragging:monitor.isDragging()
}
}
//DropTarget相关设定
const CardTarget = {
drop(props, monitor, component){ //组件放下时触发的事件
//...
},
canDrop(props,monitor){ //组件可以被放置时触发的事件,可选
//...
},
hover(props,monitor,component){ //组件在target上方时触发的事件,可选
//...
},
};
function collect1(connect,monitor) {//同DragSource的collect函数
return{
connectDropTarget:connect.dropTarget(),
isOver:monitor.isOver(), //source是否在Target上方
isOverCurrent: monitor.isOver({ shallow: true }),
canDrop: monitor.canDrop(),//能否被放置
itemType: monitor.getItemType(),//获取拖拽组件type
}
}
class CardItem extends Component{
render(){
const { isDragging, connectDragSource, connectDropTarget} = this.props;
let opacity = isDragging ? 0.1 : 1; //当被拖拽时呈现透明效果
return connectDragSource( //使用DragSource 和 DropTarget
connectDropTarget( <div>
<Card
title={this.props.title}
style={{ width: 300 ,opacity}}
>
<p>{this.props.content}</p>
</Card>
</div> )
)
}
}
// 使组件连接DragSource和DropTarget
let flow = require('lodash.flow');
export default flow(
DragSource(Types.CARD,CardSource,collect),
DropTarget(Types.CARD,CardTarget,collect1)
)(CardItem)
最后这个连接方法我参考了 reactDND官网 的说明,你可以去 lodash.flow的官网 进行查看并下载。
当然你也可以选择构造器的方法进行引用,如@DragSource(type, spec, collect)
和@DropTarget(types, spec, collect)
.
Even if you don't plan to use decorators, the partial application can
still be handy, because you can combine several DragSource and
DropTarget declarations in JavaScript using a functional composition
helper such as _.flow. With decorators, you can just stack the
decorators to achieve the same effect.
import { DragSource, DropTarget } from 'react-dnd';
import flow from 'lodash/flow';
class YourComponent {
render() {
const { connectDragSource, connectDropTarget } = this.props
return connectDragSource(connectDropTarget(
/* ... */
))
}
}
export default flow(
DragSource(/* ... */),
DropTarget(/* ... */)
)(YourComponent);
现在我们已经完成了一个拖拽效果的实现,现在我们来看一下效果
可以很明显的看到拖拽带来的效果,接下来我们要完成拖拽放置后的排序函数。
我们将排序函数放在App.js
当中,在CardItem.js
中的CardTarget
构造方法中的hover
函数中进行调用,接下来看具体的实现方法.
//CardItem.js
const CardTarget = {
hover(props,monitor,component){
if(!component) return null; //异常处理判断
const dragIndex = monitor.getItem().index;//拖拽目标的Index
const hoverIndex = props.index; //放置目标Index
if(dragIndex === hoverIndex) return null;// 如果拖拽目标和放置目标相同的话,停止执行
//如果不做以下处理,则卡片移动到另一个卡片上就会进行交换,下方处理使得卡片能够在跨过中心线后进行交换.
const hoverBoundingRect = (findDOMNode(component)).getBoundingClientRect();//获取卡片的边框矩形
const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2;//获取X轴中点
const clientOffset = monitor.getClientOffset();//获取拖拽目标偏移量
const hoverClientX = (clientOffset).x - hoverBoundingRect.left;
if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) { // 从前往后放置
return null
}
if (dragIndex > hoverIndex && hoverClientX > hoverMiddleX) { // 从后往前放置
return null
}
props.DND(dragIndex,hoverIndex); //调用App.js中方法完成交换
monitor.getItem().index = hoverIndex; //重新赋值index,否则会出现无限交换情况
}
}
//App.js
handleDND = (dragIndex,hoverIndex) => {
let CardList = this.state.CardList;
let tmp = CardList[dragIndex] //临时储存文件
CardList.splice(dragIndex,1) //移除拖拽项
CardList.splice(hoverIndex,0,tmp) //插入放置项
this.setState({
CardList
})
};
/* ...
*/
//添加传递参数传递函数
<CardItem //向次级界面传递参数
key={item.id}
title={item.title}
content={item.content}
index={index}
onDND={this.handleDND}
/>
好了,现在我们已经完成了一个卡片排序功能的小demo,让我们来看一下效果吧!
END
本人初学前端不久,刚接触react相关,这篇文章也是用于记录一下自己工作时用到的一些小功能,本文参考了
强大的拖拽组件:React DnD 的使用
和 reactDND 官网上的相关例子,一些更复杂的情况大家也可以去reactDND的官网上查看.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。