今天做自己的项目时,对于展示页轮播图顺序的修改这块地方,思前想后,觉得用拖拽排序的方式才舒服。于是折腾了一下react-dnd,特地记录下来。
react-dnd文档地址: 官方文档
一.首先
有两个依赖要安装。
cnpm i react-dnd react-dnd-html5-backend -S
要实现拖拽排序,首先需要让盒子可以被拖动,被拖动的同时还要能够接受别人的拖动,上预览:
二.相关api
DndProvider (所有的拖动操作都要在这里面)
1.属性
必填项backend,通过它将其注入后端,可以使用React-dnd随附的HTML5后端,除了自定义React-dnd后端代码。
useDrag (一个hook,使之能拖动)
1.参数(详细请阅读文档)
是一个js对象,里面必须定义item,值必须是字符串!
2.返回值
是一个js数组,在这个数组中:
1.下标0:包含来自collect函数的收集属性的对象,即参数对象中的可选成员collect,若不写则返回 {}
2.下标1:拖动源的连接器功能(类型为函数,参数类型必须是React.RefObject<any> | React.ReactElement | Element | null),把ref丢进去就完事。
3.下标2:用于拖动预览的连接器功能。这可以附加到DOM的预览部分。这里用不着
useDrop (一个hook,使之能接受拖动)
1.参数(详细请阅读文档)
是一个js对象,里面必须定义accept,值必须是字符串!
2.返回值
是一个js数组,在这个数组中:
1.下标0:包含来自collect函数的收集属性的对象,即参数对象中的可选成员collect,若不写则返回 {}
2.下标1:放置目标的连接器功能(类型为函数,参数类型同useDrag中描述),把ref丢进去就完事。
三.开始
(出了这三件套就可以打团了)
新建一个组件,用于作为拖动源和接受拖动的组件。
DragDropBox.jsx
import React, { useRef } from 'react'
import { useDrop, useDrag } from 'react-dnd'
export default ({ id, text, index, changePosition, className }) => {
const ref = useRef(null)
// 因为没有定义收集函数,所以返回值数组第一项不要
const [, drop] = useDrop({
accept: 'DragDropBox', // 只对useDrag的type的值为DragDropBox时才做出反应
hover: (item, monitor) => { // 这里用节流可能会导致拖动排序不灵敏
if (!ref.current) return
let dragIndex = item.index
let hoverIndex = index
if (dragIndex === hoverIndex) return // 如果回到自己的坑,那就什么都不做
changePosition(dragIndex, hoverIndex) // 调用传入的方法完成交换
item.index = hoverIndex // 将当前当前移动到Box的index赋值给当前拖动的box,不然会出现两个盒子疯狂抖动!
}
})
const [{ isDragging }, drag] = useDrag({
item: {
type: 'DragDropBox',
id,
index,
text
},
collect: monitor => ({
isDragging: monitor.isDragging() // css样式需要
})
})
return (
// ref 这样处理可以使得这个组件既可以被拖动也可以接受拖动
<div ref={ drag(drop(ref)) } style={{ opacity: isDragging ? 0.5 : 1 }} className={ className.dragBox }>
<h2>{`第 ${ index + 1 } 屏`}</h2>
<h2 style={{ fontSize: '30px' }}>{ id + '.' + text }</h2>
</div>
)
}
再新建一个组件,引用它。
Swipe.jsx
import React, { useState, useEffect } from 'react'
import style from './swipe.module.scss'
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import DragDropBox from './DragDropBox'
import { cloneDeep } from 'lodash'
import Card from '@/component/card/card' // 类似饿了么ui的el-card,把他当成div吧
import {
MinusOutlined
} from '@ant-design/icons'
import { Button, Popconfirm, message } from 'antd'
const initData = [
{
id: 1,
text: '111'
},
{
id: 2,
text: '222'
},
{
id: 3,
text: '333'
},
{
id: 4,
text: '444'
},
{
id: 5,
text: '555'
},
{
id: 6,
text: '666'
}
]
const Swipe = () => {
const [isChangePosition, setIsChangePosition] = useState(false)
const [boxList, setBoxList] = useState(initData)
useEffect(() => {
sessionStorage.initData = JSON.stringify(initData)
return () => {
sessionStorage.removeItem('initData')
}
}, [])
const changePosition = (dragIndex, hoverIndex) => {
let data = cloneDeep(boxList)
let temp = data[dragIndex]
// 交换位置
data[dragIndex] = data[hoverIndex]
data[hoverIndex] = temp
setBoxList(data)
}
// 更换顺序按钮
const changePositionBtn = () => {
message.info('请拖拽图片进行排序!')
setIsChangePosition(true)
}
// 取消交换位置
const cancelChangePosition = () => {
setIsChangePosition(false)
setBoxList(JSON.parse(sessionStorage.getItem('initData')))
}
// 保存修改
const saveChangePosition = () => {
setIsChangePosition(false)
sessionStorage.initData = JSON.stringify(boxList)
// 在这里发送请求更新后端数据
message.success('已更新顺序!')
}
const deleteImgOne = id => {
console.log(id)
}
return(
<div style={{ height: '100%' }}>
<Card bottom>
{
isChangePosition ?
<>
<Button onClick={ cancelChangePosition } style={{ marginRight: '15px' }}>取消</Button>
<Button type="primary" onClick={ saveChangePosition }>保存</Button>
</>
:
<Button type="primary" onClick={ changePositionBtn }>更换顺序</Button>
}
</Card>
<Card>
{
isChangePosition ?
<DndProvider backend={ HTML5Backend }>
<div className={ style.dragBoxContainer }>
{
boxList.map((value, i)=>
<DragDropBox
className={ style }
key={ value.id }
index={ i }
id={ value.id }
text={ value.text }
changePosition={ changePosition }
/>
)
}
</div>
</DndProvider>
:
<div className={ style.dragBoxContainer }>
{
boxList.map(value=> (
<div
key={ value.id }
className={ [ style.dragBox, style.deleteIcon ].join(' ') }
>
<Popconfirm
title="确定要删除这张图片吗?"
placement="top"
onConfirm={ () => deleteImgOne(value.id) }
okText="确定"
cancelText="取消"
>
<div className={ style.deleteIconBox }>
<MinusOutlined className={ style.deleteIcon }/>
</div>
</Popconfirm>
{ value.id } - { value.text }
</div>
))
}
<div className={ [style.dragBox, style.addImgBox].join(' ') }/>
</div>
}
</Card>
</div>
)
}
export default Swipe
Swipe.jsx 中的样式文件
swipe.module.scss
.card{
height: 200px;
width: 200px;
background-color: pink;
margin: 0 15px 15px 0;
}
.dragBoxContainer{
width: 100%;
height: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, 360px);
grid-template-rows: repeat(auto-fill, 180px);
grid-gap: 15px;
justify-content: center;
align-items: center;
}
.dragBox{
position: relative;
display: flex;
flex-direction: column;
height: 180px;
width: 100%;
background-color: pink;
padding: 10px;
overflow: hidden;
border-radius: 12px;
h2{
font-size: 20px;
}
.imgBox{
flex-grow: 1;
width: 100%;
background-color: #fff;
}
.deleteIconBox{
content: '';
display: block;
width: 0;
height: 0;
border-left: 40px solid transparent;
border-top: 40px solid red;
position: absolute;
right: 0;
top: 0;
cursor: pointer;
}
.deleteIcon{
color: #fff;
font-size: 16px;
position: absolute;
bottom: 18px;
right: 4px;
}
}
.addImgBox{
border: 1px dashed #ccc;
background-color: transparent;
}