实战!Antd 中使用 react-beautiful-dnd 实现列表拖拽

2,904 阅读2分钟

前言

最近项目一个配置功能菜单的模块重构,对比了 antd 社区的 react-dnd react-sortable-hoc 以及本文使用的 react-beautiful-dnd,还是觉得 rbd 比较简单明了更契合项目中使用的 Class 组件,当然项目里也用到了 hooks,本篇记录一下实现过程和遇到的问题。so, let's get started~


一、react-beautiful-dnd 的特点是什么?

物品自然优美的运动💐 无障碍:强大的键盘和屏幕阅读器支持♿️ 表现出色🚀 干净且功能强大的api,易于上手 在标准的浏览器交互中播放效果非常好 未经修饰的样式 无需创建其他包装dom节点-友好的flexbox和焦点管理!

github: github.com/atlassian/r… npm: www.npmjs.com/package/rea…

二、使用步骤

1.引入组件
packageversion
react16.12.0
antd4.11.2
react-beautiful-dnd13.0.0

代码如下:

import React, { Component, useState } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import update from 'immutability-helper';

<DragDropContext /> - 包装要启用拖放功能的组件
<Droppable /> - 包装组件开始拖拽后可以放置的区域
<Draggable /> - 包装可供拖拽的节点

2.使用组件

一级菜单拖拽组件部分,代码如下:

<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,如果用在封装的组件中,则变成获取组件的实例,将抛出以下错误:

react-beautiful-dnd 错误 为解决这个问题,如以上展示代码,我将 Draggableref 绑定到一个 div 然后定位到顶层,覆盖在 <MenuItem /> 组件之上,代替拖拽,perfect~~

顺便安利一下 immutability-helper 这个库,对于拖拽这种涉及处理复杂数据的功能确实非常方便,Antd of React Table 拖拽排序的文档里也有用到。

npm: www.npmjs.com/package/imm…

// 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 })))
}

三、效果展示

菜单排序配置

总结

项目代码,不宜过多展示,如有更好的实现建议或疑问,欢迎抢沙发~