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

前言

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


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

物品自然优美的运动💐
无障碍:强大的键盘和屏幕阅读器支持♿️
表现出色🚀
干净且功能强大的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

二、使用步骤

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

三、效果展示

菜单排序配置

总结

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

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
使用 antd table 和 react-window 实现虚拟列表时,固定列可以通过以下步骤实现: 1. 首先,在 antd table 组件设置固定列,可以通过 `fixed` 属性来实现,例如: ```jsx <Table dataSource={data} columns={columns} scroll={{ x: '100vw' }} pagination={false} rowKey="id" sticky /> ``` 其,`sticky` 属性用于开启固定列功能。 2. 然后,在 react-window 创建两个列表组件:一个用于显示固定列,另一个用于显示非固定列。我们可以使用 `FixedSizeList` 组件来实现固定列列表使用 `VariableSizeList` 组件来实现非固定列列表。例如: ```jsx // 固定列列表 const FixedColumnList = ({ height, width, columnWidth, rowCount, rowHeight, columns }) => ( <FixedSizeList height={height} width={width} itemCount={rowCount} itemSize={rowHeight} itemData={{ columns }} > {Row({ isScrolling: false })} </FixedSizeList> ); // 非固定列列表 const VariableColumnList = ({ height, width, columnWidth, rowCount, rowHeight, data, columns }) => ( <VariableSizeList height={height} width={width} itemCount={rowCount} itemSize={index => { // 根据行数据计算行高 const row = data[index]; return rowHeight(row); }} itemData={{ data, columns }} > {Row} </VariableSizeList> ); ``` 其,`Row` 组件用于渲染每一行数据,其实现可以参考 antd table 组件的 `rowRender` 方法。 3. 最后,将固定列列表和非固定列列表组合在一起,例如: ```jsx <FixedSizeList height={height} width={fixedWidth} itemCount={rowCount} itemSize={rowHeight} itemData={{ columns: fixedColumns, data }} > {Row({ isScrolling })} </FixedSizeList> <VariableSizeList height={height} width={variableWidth} itemCount={rowCount} itemSize={index => { // 根据行数据计算行高 const row = data[index]; return rowHeight(row); }} itemData={{ data, columns: variableColumns }} > {Row} </VariableSizeList> ``` 其,`fixedWidth` 和 `variableWidth` 分别表示固定列和非固定列的宽度,可以通过计算得出。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超喜欢你呦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值