因項(xiàng)目中有拖拽功能需求,于是乎在github上找到了react-beautiful-dnd這個(gè)react列表拖拽庫幫助我們實(shí)現(xiàn)甬道間拖拽骇吭,下面介紹一下react-beautiful-dnd基本的幾個(gè)API和實(shí)現(xiàn)方法。
DragDropContext
拖拽上下文纱扭÷着矗可拖拽的內(nèi)容需包裹在DragDropContext中晶衷,DragDropContext不支持嵌套。
Props
type Hooks = {|
// optional
onDragBeforeStart?: OnDragBeforeStartHook,
onDragStart?: OnDragStartHook,
onDragUpdate?: OnDragUpdateHook,
// required
onDragEnd: OnDragEndHook,
|};
type OnBeforeDragStartHook = (start: DragStart) => mixed;
type OnDragStartHook = (start: DragStart, provided: HookProvided) => mixed;
type OnDragUpdateHook = (update: DragUpdate, provided: HookProvided) => mixed;
type OnDragEndHook = (result: DropResult, provided: HookProvided) => mixed;
type Props = {|
...Hooks,
children: ?Node,
|};
基本用法
import { DragDropContext } from 'react-beautiful-dnd';
class App extends React.Component {
onDragStart = () => {
/*...*/
};
onDragUpdate = () => {
/*...*/
}
onDragEnd = () => {
// the only one that is required
};
render() {
return (
<DragDropContext
onDragStart={this.onDragStart}
onDragUpdate={this.onDragUpdate}
onDragEnd={this.onDragEnd}
>
<div>Hello world</div>
</DragDropContext>
);
}
}
Droppable
Droppable為放置拖拽元素的甬道,< Draggable/>必須包裹在<Droppable/>中抄邀。
Props
import type { Node } from 'react';
type Props = {|
// required
droppableId: DroppableId, // 必需耘眨,可拖動(dòng)甬道的唯一標(biāo)識(shí)
// optional
type?: TypeId, // string,用來簡(jiǎn)單的接受某一類draggable,當(dāng)兩個(gè)droppable的type值一樣時(shí),甬道內(nèi)的draggable才能互相拖動(dòng)
mode?: DroppableMode, // 拖動(dòng)模式境肾,默認(rèn)為standard(標(biāo)準(zhǔn))模式剔难,另一種模式為處理大量數(shù)據(jù)的virtual(虛擬)模式
isDropDisabled?: boolean, // 用于控制拖動(dòng)起來的draggable是否允許放到當(dāng)前Droppable胆屿,默認(rèn)為false(允許)
isCombineEnabled?: boolean, // 是否允許draggable合并,默認(rèn)false
direction?: Direction, // 可拖拽塊在droppable上的移動(dòng)方向钥飞,甬道為垂直的就為vertical(默認(rèn))莺掠,水平的為horizontal
ignoreContainerClipping?: boolean,
renderClone?: DraggableChildrenFn, // virtual模式中需使用
getContainerForClone?: () => HTMLElement,
children: (DroppableProvided, DroppableStateSnapshot) => Node,
|};
type DroppableMode = 'standard' | 'virtual';
type Direction = 'horizontal' | 'vertical';
// DraggableChildrenFn: 需返回一個(gè)ReactElement
<Droppable droppableId="droppable-1">
{(provided, snapshot) => ({
/*...*/
})}
</Droppable>;
placeholder
通常,我們需要將placeholder(<Droppable /> | DroppableProvided | placeholder)
放入列表中读宙,以便在拖動(dòng)過程中根據(jù)需要在列表中插入空格彻秆。
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{/* Usually needed. But not for virtual lists! */}
{provided.placeholder}
</div>
)}
</Droppable>
Tips: 在虛擬列表中我們不需要加入placeholder
占位符,因?yàn)樘摂M列表中我們不是基于可視項(xiàng)的集合大小來確定列表的尺寸结闸,而是根據(jù)itemCount
來計(jì)算的唇兑。(height = itemSize*itemCount
)。對(duì)于虛擬列表桦锄,將我們自己的節(jié)點(diǎn)插入其中不會(huì)增加列表的大小扎附。
Draggable
Draggable 為可拖拽的塊,<Draggable/>必須放在<Droppable/>里结耀。
Props
import type { Node } from 'react';
type Props = {|
// required
draggableId: DraggableId, // 可拖拽塊的唯一標(biāo)識(shí)id
index: number, // index索引
children: DraggableChildrenFn,
// optional
isDragDisabled: ?boolean, // 是否允許該draggable被拖動(dòng)
disableInteractiveElementBlocking: ?boolean,
shouldRespectForcePress: ?boolean,
|};
基本用法
const getItems = count =>
Array.from({ length: count }, (v, k) => k).map(k => ({
id: `item-${k}`,
content: `item-${k}`
}))
const grid = 8;
const getItemStyle = (isDragging, draggableStyle) => ({
// some basic styles to make the items look a bit nicer
userSelect: "none",
padding: grid * 2,
margin: `0 0 ${grid}px 0`,
// change background color if dragging
background: isDragging ? "lightgreen" : "grey",
// styles we need to apply on draggables
...draggableStyle
});
const getListStyle = isDraggingOver => ({
background: isDraggingOver ? "lightblue" : "lightgrey",
padding: grid,
width: 250
});
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="drop">
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
>
{getItems(10).map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
{item.content}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
拖拽圖示
virtual模式
當(dāng)數(shù)據(jù)量足夠大的時(shí)候留夜,相應(yīng)地渲染出來的dom也會(huì)足夠的多, react-virtualized
便是一個(gè)react長(zhǎng)列表解決方案。
react-beattiful-dnd@12.0
版本也增加了對(duì)虛擬列表的支持图甜。
虛擬列表通過判斷并只加載當(dāng)前視窗內(nèi)的列表元素來解決海量數(shù)據(jù)列表碍粥。
自行引入
react-virtualized
或react-window
,使用他們的一些支持虛擬列表的組件黑毅。首先要把
<Droppable/>
的mode屬性設(shè)為virtual
(參考上文Droppable的props)嚼摩,告訴DragDropContext當(dāng)前甬道為虛擬列表模式。使用
<Droppable/>
的renderClone
API 矿瘦。在虛擬列表模式下枕面,拖動(dòng)時(shí)原始<Draggable/>
會(huì)被刪除,然后用renderClone克隆個(gè)新的放到容器元素中缚去。
renderClone用法:
function List(props) {
const items = props.items;
return (
<Droppable
droppableId="droppable"
renderClone={(provided, snapshot, rubric) => (
<div
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
Item id: {items[rubric.source.index].id}
</div>
)}
>
{provided => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{items.map(item) => (
<Draggable draggableId={item.id} index={item.index}>
{(provided, snapshot) => (
<div
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
Item id: {item.id}
</div>
)}
</Draggable>
)}
</div>
)}
</Droppable>
);
}
或
const getRenderItem = (items) => (provided, snapshot, rubric) => (
<div
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
Item id: {items[rubric.source.index].id}
</div>
);
function List(props) {
const items = props.items;
const renderItem = getRenderItem(items);
return (
<Droppable
droppableId="droppable"
renderClone={renderItem}
>
<div ref={provided.innerRef} {...provided.droppableProps}>
{items.map(item) => (
<Draggable draggableId={item.id} index={item.index}>
{renderItem}
</Draggable>
)}
</div>
</Droppable>
);
}
Tips: 在使用react-virtualized時(shí)潮秘,稍不注意會(huì)出現(xiàn)滾動(dòng)出第一屏后頁面閃爍的問題。
拖動(dòng)的原理——數(shù)組的重排
onDragEnd
該鉤子是拖拽過程中最重要的一個(gè)函數(shù)易结,也是必需的唇跨,該函數(shù)必須導(dǎo)致列表數(shù)據(jù)的重新排序。它也提供來有關(guān)拖動(dòng)的所有信息衬衬。
result:DropResult
type DropResult = {|
...DragUpdate,
reason: DropReason,
|}
type DropReason = 'DROP' | 'CANCEL';
-
result.draggableId
: 拖動(dòng)的draggable
的draggableId
-
result.type
: 拖動(dòng)的draggable
的類型(type)买猖,droppable
上設(shè)置的type值 -
result.source
:draggable
的起始位置(包含起始位置的index索引和droppableId) -
result.destination
:draggable
完成的位置,如果用戶在超過<Droppable/>
的情況下掉落滋尉,則目標(biāo)將為null(如果不為null玉控,則包含結(jié)束位置的index索引和droppableId) -
result.reason
: 下降的原因
你需要做的
- 如果
result.destination
為null,直接return; - 如果
source.droppableId
和destination.droppableId
相等狮惜,則需要從列表中刪除該項(xiàng)目并放置到正確的位置高诺; - 如果
source.droppableId
和destination.droppableId
不相等碌识,則需要source.droppableId
列表中刪除該項(xiàng)目并放置到destination.droppableId
正確的位置;
附上我的demo代碼庫虱而,有興趣的可以看看筏餐。demo代碼庫
另附上react-beautiful-dnd官方地址。