因项目中有拖拽功能需求,于是乎在github上找到了react-beautiful-dnd这个react列表拖拽库帮助我们实现甬道间拖拽,下面介绍一下react-beautiful-dnd基本的几个API和实现方法。
DragDropContext
拖拽上下文。可拖拽的内容需包裹在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, // 必需,可拖动甬道的唯一标识
// optional
type?: TypeId, // string,用来简单的接受某一类draggable,当两个droppable的type值一样时,甬道内的draggable才能互相拖动
mode?: DroppableMode, // 拖动模式,默认为standard(标准)模式,另一种模式为处理大量数据的virtual(虚拟)模式
isDropDisabled?: boolean, // 用于控制拖动起来的draggable是否允许放到当前Droppable,默认为false(允许)
isCombineEnabled?: boolean, // 是否允许draggable合并,默认false
direction?: Direction, // 可拖拽块在droppable上的移动方向,甬道为垂直的就为vertical(默认),水平的为horizontal
ignoreContainerClipping?: boolean,
renderClone?: DraggableChildrenFn, // virtual模式中需使用
getContainerForClone?: () => HTMLElement,
children: (DroppableProvided, DroppableStateSnapshot) => Node,
|};
type DroppableMode = 'standard' | 'virtual';
type Direction = 'horizontal' | 'vertical';
// DraggableChildrenFn: 需返回一个ReactElement
<Droppable droppableId="droppable-1">
{(provided, snapshot) => ({
/*...*/
})}
</Droppable>;
placeholder
通常,我们需要将placeholder(<Droppable /> | DroppableProvided | placeholder)
放入列表中,以便在拖动过程中根据需要在列表中插入空格。
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{/* Usually needed. But not for virtual lists! */}
{provided.placeholder}
</div>
)}
</Droppable>
Tips: 在虚拟列表中我们不需要加入placeholder
占位符,因为虚拟列表中我们不是基于可视项的集合大小来确定列表的尺寸,而是根据itemCount
来计算的。(height = itemSize*itemCount
)。对于虚拟列表,将我们自己的节点插入其中不会增加列表的大小。
Draggable
Draggable 为可拖拽的块,<Draggable/>必须放在<Droppable/>里。
Props
import type { Node } from 'react';
type Props = {|
// required
draggableId: DraggableId, // 可拖拽块的唯一标识id
index: number, // index索引
children: DraggableChildrenFn,
// optional
isDragDisabled: ?boolean, // 是否允许该draggable被拖动
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模式
当数据量足够大的时候,相应地渲染出来的dom也会足够的多, react-virtualized
便是一个react长列表解决方案。
react-beattiful-dnd@12.0
版本也增加了对虚拟列表的支持。
虚拟列表通过判断并只加载当前视窗内的列表元素来解决海量数据列表。
自行引入
react-virtualized
或react-window
,使用他们的一些支持虚拟列表的组件。首先要把
<Droppable/>
的mode属性设为virtual
(参考上文Droppable的props),告诉DragDropContext当前甬道为虚拟列表模式。使用
<Droppable/>
的renderClone
API 。在虚拟列表模式下,拖动时原始<Draggable/>
会被删除,然后用renderClone克隆个新的放到容器元素中。
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时,稍不注意会出现滚动出第一屏后页面闪烁的问题。
拖动的原理——数组的重排
onDragEnd
该钩子是拖拽过程中最重要的一个函数,也是必需的,该函数必须导致列表数据的重新排序。它也提供来有关拖动的所有信息。
result:DropResult
type DropResult = {|
...DragUpdate,
reason: DropReason,
|}
type DropReason = 'DROP' | 'CANCEL';
-
result.draggableId
: 拖动的draggable
的draggableId
-
result.type
: 拖动的draggable
的类型(type),droppable
上设置的type值 -
result.source
:draggable
的起始位置(包含起始位置的index索引和droppableId) -
result.destination
:draggable
完成的位置,如果用户在超过<Droppable/>
的情况下掉落,则目标将为null(如果不为null,则包含结束位置的index索引和droppableId) -
result.reason
: 下降的原因
你需要做的
- 如果
result.destination
为null,直接return; - 如果
source.droppableId
和destination.droppableId
相等,则需要从列表中删除该项目并放置到正确的位置; - 如果
source.droppableId
和destination.droppableId
不相等,则需要source.droppableId
列表中删除该项目并放置到destination.droppableId
正确的位置;
附上我的demo代码库,有兴趣的可以看看。demo代码库
另附上react-beautiful-dnd官方地址。