當(dāng)我還在使用 react-dnd 設(shè)計(jì)拖拽邏輯和交互、當(dāng)我還在為計(jì)算拖拽元素和 hover 元素的位置坐標(biāo)而煩躁不已,當(dāng)我還在為即將到來(lái)的 deadline 發(fā)愁之際谐腰,我發(fā)現(xiàn)了 react-beautiful-dnd。寫(xiě)了個(gè) demo 試了下废封,真香!
先上代碼
話不多說(shuō)丧蘸,先上代碼漂洋。(我等伸手黨福音~(yú))
如果懶得看代碼,這里是 >>Github 地址<<
安裝所需庫(kù):
$ 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: '睡覺(jué)' }, { id: 3, name: '打豆豆' }],
},
{
id: 200,
name: 'doing',
issues: [{ id: 4, name: '刪庫(kù)' }, { 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;
}
效果是這樣子的:
示例
因?yàn)槲铱戳司W(wǎng)上的拖拽 demo 都不夠精美力喷,有些代碼也比較老了刽漂,所以貼個(gè)最新的希望能對(duì)大家有所幫助吧。
簡(jiǎn)單介紹 API
詳細(xì) API 就不人肉翻譯啦~沒(méi)啥意義弟孟,看 >>這里<<就好贝咙。
PS:一開(kāi)始在首頁(yè) README 找 API 居然沒(méi)有找到,卻跑到了一個(gè)教學(xué)視頻網(wǎng)站去了拂募。把我給氣的……后來(lái)才想起來(lái)按道理會(huì)有一個(gè)叫
doc
的目錄 - -
簡(jiǎn)單說(shuō)下我用到的 API:
-
<DragDropContext />
是為了給拖拽提供上下文的庭猩,只有在<DragDropContext />
中去寫(xiě)拖拽才是有效的窟她。這里注意它必須要一個(gè) onDragEnd 的 props 來(lái)操作拖拽結(jié)束事件。其他事件有如下蔼水,具體說(shuō)用看一眼就知道震糖。
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 />
,它用來(lái)定義放置拖拽元素的容器徙缴。它有一個(gè)必填屬性droppableId
试伙,另外它的children
屬性被定義成了一個(gè)函數(shù),函數(shù)提供的 provided 用來(lái)綁定 DOM 節(jié)點(diǎn)于样,提供的 snapshot 可以讓我們獲取當(dāng)前放置容器的屬性和狀態(tài)。
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 />
包含的東西就是那個(gè)拖拽元素啦潘靖。它必須包含有draggableId
穿剖、index
和children
三個(gè)屬性。它和<Droppable />
類似也是將children
定義為了函數(shù)卦溢,函數(shù)提供的 provided 用來(lái)綁定 DOM 節(jié)點(diǎn)糊余,提供的 snapshot 可以讓我們獲取當(dāng)前放置容器的屬性和狀態(tài)。
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>;
注意单寂,三者是包含關(guān)系贬芥,必須逐層實(shí)現(xiàn)才能夠做到拖拽哦。所以呈現(xiàn)的 DOM 結(jié)構(gòu)樣子應(yīng)該是醬紫的:
<DragDropContext>
<Droppable>
<Draggable></Draggable>
<Draggable></Draggable>
<Draggable></Draggable>
......
</Droppable>
<Droppable>
<Draggable></Draggable>
<Draggable></Draggable>
......
</Droppable>
......
</DragDropContext>