自定义列表格

480 阅读6分钟

背景介绍

文章 pro-components 踩坑 中介绍了 @ant-design/pro-components 中的ProTable正好具备自定义列的功能,然而由于ProComponents功能全面且强大,导致内部包高耦合,包的大小很大,安装和打包需要占用大量的时间,虽然可以通过在config.ts中配置externals来解决,externals通过 排除某些 import 的包(package)打包到bundle中,而在运行时(runtime)再去从外部获取扩展依赖,可以大幅度提高打包效率,而外部依赖可以通过cdn引入提高加载速度。
但是外部引入cdn 存在一定的安全隐患,同时外部加载cdn依赖容易 受到用户侧网络问题、以及 cdn域名访问速度过慢的问题从而导致 一定的白屏时间占用;另一个问题是通过externals的方式配置cdn引入,一旦改了package.json中某个包的版本,对应的cdn引入文件也要手动修改,操作较为繁琐,因此手写自定义列表格可能是最优解,而自定义列最复杂、繁琐的功能莫过于拖拽,这里选取了@dnd-kit 作为拓展工具包。

@dnd-kit 介绍

dnd kit 是一个用于 React 的现代、轻量级、高性能、可访问和可扩展的拖放工具包。

  • 为 React 构建: 公开诸如 useDraggableuseDroppable之类的 hooks,并且不需要你重新构建应用程序或创建额外的包装器 DOM 节点。
  • 功能丰富: 可定制的碰撞检测算法、多个激活器、可拖动覆盖、拖动手柄、自动滚动、约束等等。
  • 支持广泛的用例: 列表、网格、多个容器、嵌套上下文、可变大小的项目、虚拟化列表、2D 游戏等。
  • 零依赖和模块化: 库的核心重约 10kb,没有外部依赖。它围绕内置的 React 状态管理和上下文构建,从而使库保持精简。
  • 内置支持多种输入法: 指针、鼠标、触摸和键盘传感器。
  • 完全可定制和可扩展: 定制每个细节:动画、过渡、行为、样式。构建你自己的传感器、碰撞检测算法、自定义键绑定等等。
  • 辅助功能: 键盘支持、合理的默认咏叹调属性、可定制的屏幕阅读器说明和内置实时区域。
  • 性能: 它的构建考虑了性能,以支持丝般流畅的动画。
  • 预设: 需要构建一个可排序的界面?Check out @dnd-kit/sortable,这是建立在 @dnd-kit/core 上面的 thin layer。未来会有更多预设。

dnd kit 拖拽使用说明

  1. 使用前提

下载以下5个依赖:
@dnd-kit/core
@dnd-kit/accessibility
@dnd-kit/sortable
@dnd-kit/modifiers
@dnd-kit/utilities

  1. 标签/组件介绍

1、DndContext:dnd kit的容器,使用拖拽操作必须要有,并且滑动列表的拖拽事件都在这里写(onDragMove,onDragEnd等等)
2、SortableContext:滑动列表的容器,相当于ul,内部的列表配置好了可以拖拽(items为必填项,表示哪些列表在我这个容器中滑动,items为唯一标识的数组)

3、useSortable:将唯一标志变为一个列表的内部项,即将SortableContext中提供的items的子项渲染,相当于li,使用方式:useSortable({id:XXX})。
返回值说明: 1、setNodeRef:关联dom节点,使其成为一个可拖拽的项;
2、listeners:包含onKeyDown,onPointerDown方法,主要让节点可以进行拖拽;
3、transform:该节点被拖动时候的移动变化值;
4、transition:过渡效果;
5、isDragging:节点是否在拖拽
4、sensors:dndkit提供的传感器,默认是使用所有传感器,包括鼠标、键盘等
5、DragOverlay:相当于不更改原对象,当拖拽时会复制一个新的div
6、collisionDetection:碰撞算法,具体可查看相关文档
7、arrayMove:拖拽后交换数组的位置,进行上移和下移,内部代码如下:
function arrayMove(array, from, to) {

const newArray = array.slice();

newArray.splice(to < 0 ? newArray.length + to : to, 0, newArray.splice(from, 1)[0]);

return newArray;

}

  1. 简单demo代码:
import { DndContext } from "@dnd-kit/core";
import { arrayMove, SortableContext, useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useState } from "react";

// 容器组件
export default function SingleTest() {
    const [items,setItems] = useState(["A","B","C"])

    // 拖拽结束后的操作
    function dragEndEvent(props:any) {
        const { active,over } = props
        const activeIndex = items.indexOf(active.id)
        const overIndex = items.indexOf(over.id)
        setItems(items=>{
            return arrayMove(items,activeIndex,overIndex)
        })
    }

    return (
        <DndContext onDragEnd={dragEndEvent}>
            <SortableContext items={items}>
                {
                    items.map(val=>(<Item id={val}/>))
                }
            </SortableContext>
        </DndContext>
    )
}

// 拖拽项组件
function Item(props:any) {
    const { id } = props
    const {setNodeRef,listeners,transform,transition } = useSortable({id})
    const styles = {
        transform:CSS.Transform.toString(transform),
        border: "1px solid red",
        marginTop: "10px"
    }

    return (
        <div ref={ setNodeRef } {...listeners} style={styles}>{id}</div>
    )
}

注意事项:

  1. 点击事件失效

    原因:点击事件onClick和拖拽事件的onPointerDown有部分重叠,导致点击的时候系统无法准确的知道你是click还是pointerDown

    解决方法:测试下来方法2效果更加

    //1.添加 sensor 传感器,增加一个延迟。
    const sensors = useSensors(useSensor(PointerSensor,{
        activationConstraint: {
          delay: 100,
          tolerance: 0,
        }
    }))
    //2.使用传感器,设置鼠标传感器,当距离小于5时不响应拖拽事件
    const sensors = useSensors(useSensor(PointerSensor,{
        activationConstraint: {
            distance: 5, 
        }
    }))
    
  2. 解决系统自定义CSS方法带来的问题

    // 只需要重写单个拖拽项的styles即可(弃用系统自带CSS方法)
    function Item(props:any) {
        const { id } = props
        const {setNodeRef,listeners,transform,transition } = useSortable({id})
        const styles = isDragging?{
            transform: `translate3d(0px, ${transform?.y}px, 0) scaleX(1) scaleY(1)`,
            border: "1px solid red",
            marginTop: "10px"
        }:undefined
    
        return (
            <div ref={ setNodeRef } {...listeners} style={styles}>{id}</div>
        )
    
    }
    
  3. 子项item使用checkbox时,拖拽前点击正常,拖拽后点击事件失效
    可能原因:由于代码中通过checkbox.group 组件统一管理checkbox,拖拽时会重新复制子项的chebox,从而产生了子项checkbox的onClick、onChange事件丢失、失效的现象。

    解决方法:取消checkbox.group 组件统一管理checkbox,单独对每个checkbox设置onClick、onChange事件

     <Checkbox
      className="checkbox"
      value={item.id}
      checked={checkList.includes(item.id)} 
      onChange={() => checkChange(item.id)} //单独设置
    >
      {item.title}
    </Checkbox>
    
  4. window、mac系统 滚动条样式问题

    原因:在不同的操作系统中,浏览器的滚动条样式的差异,主要是由于操作系统本身的设计和样式导致的。在Windows和macOS中,滚动条的样式是不同的。在macOS中,滚动条是不占据屏幕尺寸的,而在Windows中,滚动条占据一定的屏幕尺寸

    解决方法:如果我们想要在不同的操作系统中,使浏览器的滚动条样式一致,我们可以使用CSS来自定义滚动条。在全局的CSS样式中,我们可以使用::-webkit-scrollbar来定义滚动条整体的样式,使用::-webkit-scrollbar-thumb来定义滚动条拇指的样式,使用::-webkit-scrollbar-track来定义滚动条轨道的样式。

     /* 滚动条,抹平不同系统差异 */
      .list-group::-webkit-scrollbar {
        /* 隐藏默认的滚动条 */
        -webkit-appearance: none;
      }
    
      .list-group::-webkit-scrollbar:vertical {
        /* 设置垂直滚动条宽度 */
        width: 6px;
      }
    
      .list-group:hover::-webkit-scrollbar-thumb:vertical {
        /* 滚动条的其他样式定制,注意,这个一定也要定制,否则就是一个透明的滚动条 */
        border-radius: 6px;
        // border: 8px solid rgba(255, 255, 255, 0.5);
        background-color: rgba(0, 0, 0, 0.4);
      }
    

自定义列实现原理

查看ProTable源码得通过三个字段{"show":true,"fixed":"left","order":0}控制自定义列,即show控制显示与否,fixed控制左右固定,order控制字段显示顺序,核心代码位置如下

手写的自定义列借鉴了ProTable的实现,也通过{"show":true,"fixed":"left","order":0}三个字段控制显示、固定、展示顺序。

github 源码地址

github.com/982528494/c…

采用的ts编写,ts较为薄弱,any较多,大佬们多多包含😊,使用中如若存在问题call me,目前测试次数较少。

重要的事情说三遍:求star😄!

参考链接

docs.dndkit.com/introductio…
procomponents.ant.design/components/…
juejin.cn/post/716652…
juejin.cn/post/709386…