当我还在使用 react-dnd 设计拖拽逻辑和交互、当我还在为计算拖拽元素和 hover 元素的位置坐标而烦躁不已,当我还在为即将到来的 deadline 发愁之际,我发现了 react-beautiful-dnd。写了个 demo 试了下,真香!
先上代码
话不多说,先上代码。(我等伸手党福音~)
如果懒得看代码,这里是 >>Github 地址<<
安装所需库:
$ yarn add react-beautiful-dnd
$ yarn add @types/react-beautiful-dnd
下面是代码:
import React, { useState } from 'react'
import { DragDropContext, Droppable, Draggable, DropResult, DraggableProvided, DraggableStateSnapshot } from 'react-beautiful-dnd'
import update from 'immutability-helper'
import styles from './index.less'
interface initialDataInferface {
id: number;
name: string;
issues: {
id: number;
name: string
}[]
}
interface ColumnProps {
columnIndex: number
column: initialDataInferface
}
interface IssueProps {
id: number
issueIndex: number,
name: string
}
const InitialData: initialDataInferface[] = [
{
id: 100,
name: 'todo',
issues: [{ id: 1, name: '吃饭' }, { id: 2, name: '睡觉' }, { id: 3, name: '打豆豆' }],
},
{
id: 200,
name: 'doing',
issues: [{ id: 4, name: '删库' }, { id: 5, name: '跑路' }]
},
{
id: 300,
name: 'done',
issues: []
}
]
const Issue = (props: IssueProps) => {
const { id, issueIndex, name } = props
return (
<Draggable draggableId={`${id}`} index={issueIndex}>
{(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => (
<div
ref={provided.innerRef}
className={snapshot.isDragging ? styles.issueDragging : styles.issue}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{name}
</div>
)}
</Draggable>
)
}
const Column = (props: ColumnProps) => {
const { columnIndex, column } = props
const { issues } = column
return (
<div className={styles.column}>
<div className={styles.columnTitle}>
{column.name}({column.issues.length})
</div>
<Droppable droppableId={`${columnIndex}`}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
className={snapshot.isDraggingOver ? styles.columnContentActive : styles.columnContent}
{...provided.droppableProps}
>
{issues.map((issue, index) => (
<Issue key={issue.id} issueIndex={index} id={issue.id} name={issue.name} />
))}
{provided.placeholder}
</div>
)}
</Droppable>
</div>
)
}
export default () => {
const [data, setData] = useState(InitialData)
const onDragEnd = (result: DropResult) => {
const { destination, source } = result
if (!destination) {
return
}
const fromColumnIndex = Number(source.droppableId)
const fromIssueIndex = source.index
const toColumnIndex = Number(destination.droppableId)
const toIssueIndex = destination.index
const TempIssue = data[fromColumnIndex].issues[fromIssueIndex]
let TempData = update(data, {
[fromColumnIndex]: {
issues: issues =>
update(issues, {
$splice: [[fromIssueIndex, 1]]
})
}
})
TempData = update(TempData, {
[toColumnIndex]: {
issues: issues =>
update(issues, {
$splice: [[toIssueIndex, 0, TempIssue]]
})
}
})
setData(TempData)
}
return (
<DragDropContext onDragEnd={onDragEnd}>
<div className={styles.container}>
{data.map((column, index) => {
return <Column columnIndex={index} key={column.id} column={column} />
})}
</div>
</DragDropContext>
)
}
为了好看加了些样式:
.container {
display: flex;
height: 640px;
background: #f7f7f7;
}
.column {
display: inline-block;
width: 292px;
height: 640px;
}
.columnTitle {
color: #383838;
font-size: 14px;
font-weight: 600;
line-height: 40px;
margin: 0 10px;
}
.columnContent {
height: 600px;
overflow: auto;
}
.columnContentActive {
overflow: auto;
height: 600px;
background: #ccecff;
border: 2px solid #1b9aee;
}
.issue {
position: relative;
min-height: 20px;
padding: 14px 44px;
background: #ffffff;
margin: 8px 10px;
}
.issueDragging {
position: relative;
min-height: 20px;
padding: 14px 44px;
background: #ffffff;
opacity: 0.9;
margin: 8px 10px;
}
效果是这样子的:
示例
因为我看了网上的拖拽 demo 都不够精美,有些代码也比较老了,所以贴个最新的希望能对大家有所帮助吧。
简单介绍 API
详细 API 就不人肉翻译啦~没啥意义,看 >>这里<<就好。
PS:一开始在首页 README 找 API 居然没有找到,却跑到了一个教学视频网站去了。把我给气的……后来才想起来按道理会有一个叫
doc
的目录 - -
简单说下我用到的 API:
-
<DragDropContext />
是为了给拖拽提供上下文的,只有在<DragDropContext />
中去写拖拽才是有效的。这里注意它必须要一个 onDragEnd 的 props 来操作拖拽结束事件。其他事件有如下,具体说用看一眼就知道。
onBeforeCapture = () => {
/*...*/
};
onBeforeDragStart = () => {
/*...*/
};
onDragStart = () => {
/*...*/
};
onDragUpdate = () => {
/*...*/
};
onDragEnd = () => {
// the only one that is required
};
render() {
return (
<DragDropContext
onBeforeCapture={this.onBeforeCapture}
onBeforeDragStart={this.onBeforeDragStart}
onDragStart={this.onDragStart}
onDragUpdate={this.onDragUpdate}
onDragEnd={this.onDragEnd}
>
<div>Hello world</div>
</DragDropContext>
);
}
- 然后是
<Droppable />
,它用来定义放置拖拽元素的容器。它有一个必填属性droppableId
,另外它的children
属性被定义成了一个函数,函数提供的 provided 用来绑定 DOM 节点,提供的 snapshot 可以让我们获取当前放置容器的属性和状态。
import { Droppable } from 'react-beautiful-dnd';
<Droppable droppableId="droppable-1" type="PERSON">
{(provided, snapshot) => (
<div
ref={provided.innerRef}
style={{ backgroundColor: snapshot.isDraggingOver ? 'blue' : 'grey' }}
{...provided.droppableProps}
>
<h2>I am a droppable!</h2>
{provided.placeholder}
</div>
)}
</Droppable>;
-
<Draggable />
包含的东西就是那个拖拽元素啦。它必须包含有draggableId
、index
和children
三个属性。它和<Droppable />
类似也是将children
定义为了函数,函数提供的 provided 用来绑定 DOM 节点,提供的 snapshot 可以让我们获取当前放置容器的属性和状态。
import { Draggable } from 'react-beautiful-dnd';
<Draggable draggableId="draggable-1" index={0}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<h4>My draggable</h4>
</div>
)}
</Draggable>;
注意,三者是包含关系,必须逐层实现才能够做到拖拽哦。所以呈现的 DOM 结构样子应该是酱紫的:
<DragDropContext>
<Droppable>
<Draggable></Draggable>
<Draggable></Draggable>
<Draggable></Draggable>
......
</Droppable>
<Droppable>
<Draggable></Draggable>
<Draggable></Draggable>
......
</Droppable>
......
</DragDropContext>