最近项目一个配置功能菜单的模块重构,对比了 antd 社区的 react-dnd react-sortable-hoc 以及本文使用的 react-beautiful-dnd,还是觉得 rbd 比较简单明了更契合项目中使用的 Class 组件,当然项目里也用到了 hooks,本篇记录一下实现过程和遇到的问题。so, let’s get started~
物品自然优美的运动
无障碍:强大的键盘和屏幕阅读器支持♿️
表现出色
干净且功能强大的api,易于上手
在标准的浏览器交互中播放效果非常好
未经修饰的样式
无需创建其他包装dom节点-友好的flexbox和焦点管理!
github: https://github.com/atlassian/react-beautiful-dnd/blob/HEAD/docs/support/media.md
npm: https://www.npmjs.com/package/react-beautiful-dnd
package | version |
---|---|
react | 16.12.0 |
antd | 4.11.2 |
react-beautiful-dnd | 13.0.0 |
代码如下:
import React, { Component, useState } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import update from 'immutability-helper';
- 包装要启用拖放功能的组件
- 包装组件开始拖拽后可以放置的区域
- 包装可供拖拽的节点
一级菜单拖拽组件部分,代码如下:
<DragDropContext onDragEnd={this.onFirstMenuDragEnd}>
<Droppable droppableId="droppable" direction="horizontal">
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.droppableProps} style={{
height: 50,
borderTop: 'solid 1px #d9d9d9',
width: '100%',
...getListStyle(snapshot.isDraggingOver)
}}>
{ menus.map((menu, index) => {
return (
<Draggable key={index} draggableId={'Draggable' + index} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
position: 'relative', flex: 1,
width: 0, height: '100%',
...getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)
}}
>
<div style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 2000
}} />
<MenuItem
sorted={sorted}
key={index}
menus={menus}
menu={menu}
menuIndex={index}
onChange={(value, cb) => this.setState({ menus: value }, _ => cb && cb())}
onSelect={_ => null}
activeNode={activeNode}
FormattedMessage={this.FormattedMessage}
/>
</div>
)}
</Draggable>
)
})}
{provided.placeholder}
{!menus.length && sorted && (
<Space style={{ width: '100%', justifyContent: 'center' }}>
<FormattedMessage id="CLIENT_MENUMAKER_DRAWER_MENU_NULL_TIPS" />
<SmileOutlined />
</Space>
)}
</div>
)}
</Droppable>
</DragDropContext>
Droppable 使用 direction="horizontal || vertical"
可以设置拖拽列表方向。
拖拽样式如下:
const getListStyle = isDraggingOver => ({
background: isDraggingOver ? '#e6f7ff' : 'transparent',
display: 'flex',
overflow: 'auto',
});
const getItemStyle = (isDragging, draggableStyle) => ({
// some basic styles to make the items look a bit nicer
userSelect: 'none',
// change background colour if dragging
background: isDragging ? '#91d5ff' : '#fff',
// styles we need to apply on draggables
opecity: isDragging ? 0.5 : 1,
...draggableStyle
});
注意!
Droppable 和 Draggable 组件的 provided.innerRef 属性只能直接绑定 HTML Element,如果用在封装的组件中,则变成获取组件的实例,将抛出以下错误:
为解决这个问题,如以上展示代码,我将Draggable
的ref
绑定到一个div
然后定位到顶层,覆盖在组件之上,代替拖拽,perfect~~
顺便安利一下 immutability-helper
这个库,对于拖拽这种涉及处理复杂数据的功能确实非常方便,Antd of React Table 拖拽排序的文档里也有用到。
npm: https://www.npmjs.com/package/immutability-helper
// Drag callback
onFirstMenuDragEnd = (res) => {
const { menus } = this.state;
const { destination, source } = res;
if (!destination) return;
const dragRow = menus[source.index];
const sortedMenus = update(menus, {
$splice: [
[source.index, 1],
[destination.index, 0, dragRow],
],
});
const { reply, action, menu } = dragRow;
let tips = '';
if (reply) tips = reply.displayText;
if (action) tips = action.displayText;
if (menu) tips = menu.displayText;
this.setState({ menus: sortedMenus }, _ => message.success(this.FormattedMessage('CLIENT_MENUMAKER_MENU_NOTIFICATION_SORTED', { text: tips })))
}
项目代码,不宜过多展示,如有更好的实现建议或疑问,欢迎抢沙发~