WHAT - Table 表格组件实践 - 拖拽列改变宽度 Resizable Column

目录

  • 实现关键
  • 设计思路
  • 具体代码
  • 卡顿现象
    • 提升性能的优化方法
      • [1. 使用 `React.memo` 或 `PureComponent`](#1. 使用 React.memoPureComponent)
      • [2. 最小化重新渲染](#2. 最小化重新渲染)
      • [3. 使用虚拟化](#3. 使用虚拟化)
      • [4. 性能监测和优化](#4. 性能监测和优化)
    • [改进后的 `withResizableColumns` 示例](#改进后的 withResizableColumns 示例)
    • 总结

实现关键

  1. 为列头添加可拖拽的交互
  2. 拖拽时监听鼠标事件移动偏移值
  3. 计算得到被操作列具体宽度
  4. 最后赋值 columns 给 antd Table

设计思路

实现一个高阶组件,使用时将外部业务 Table 组件传入即可:

typescript 复制代码
export const CommonTable = withResizableColumns((data, columns, ...rest) => return <Table columns={columns} dataSource={data} {...res} />)

具体代码

typescript 复制代码
import React, { useState, useEffect } from "react"
import type { ResizeCallbackData } from "react-resizable"
import { Resizable } from "react-resizable"
import styles from "./index.module.less"

interface WithResizableColumnsProps {
    columns: any[]
}

const ResizableTitle = (
    props: React.HTMLAttributes<any> & {
        onResize: (
            e: React.SyntheticEvent<Element>,
            data: ResizeCallbackData
        ) => void
        width: number
    }
) => {
    const { onResize, width, ...restProps } = props
    if (!width) {
        return <th {...restProps} />
    }
    return (
        <Resizable
            width={width}
            height={0}
            handle={
                <span
                    className="react-resizable-handle"
                    onClick={(e) => {
                        e.stopPropagation()
                    }}
                />
            }
            onResize={onResize}
            draggableOpts={{ enableUserSelectHack: false }}
        >
            <th {...restProps} />
        </Resizable>
    )
}

const withResizableColumns = (WrappedComponent: React.ComponentType<any>) => {
    return (props: WithResizableColumnsProps) => {
        const { columns: initialColumns } = props
        const [columns, setColumns] = useState(initialColumns)

        useEffect(() => {
            setColumns(initialColumns)
        }, [initialColumns])

        const handleResize =
            (index: number) =>
            (
                _: React.SyntheticEvent<Element>,
                { size }: ResizeCallbackData
            ) => {
                const newColumns = [...columns]
                newColumns[index] = {
                    ...newColumns[index],
                    width: size.width,
                }
                setColumns(newColumns)
            }

        const mergedColumns = columns.map((col, index) => ({
            ...col,
            onHeaderCell: (column: any) => ({
                width: column.width,
                onResize: handleResize(index) as React.ReactEventHandler<any>,
            }),
        }))

        return (
            <WrappedComponent
                {...props}
                columns={mergedColumns}
                components={{
                    header: {
                        cell: ResizableTitle,
                    },
                }}
                className={styles.resizableTableContainer}
            />
        )
    }
}

export default withResizableColumns

卡顿现象

在使用 withResizableColumns 高阶组件来实现列拖拽调整宽度时,确实可能会遇到性能卡顿的问题,尤其是在处理复杂的列或大量数据时。

性能问题通常与以下因素有关:

  1. 频繁的重新渲染:每次列宽调整都会触发组件的重新渲染。
  2. 复杂的列配置:复杂的列设置可能会导致性能下降。
  3. React 组件的复杂性:复杂的组件树和事件处理可能会增加渲染的负担。

提升性能的优化方法

下面是一些提升 withResizableColumns 性能的建议和优化措施:

1. 使用 React.memoPureComponent

确保组件在列宽调整时不会不必要地重新渲染。使用 React.memoPureComponent 来优化 ResizableTitle 组件的性能。

jsx 复制代码
import React, { memo } from 'react';
import { Resizable } from 'react-resizable';

// 使用 React.memo 优化 ResizableTitle 组件
const ResizableTitle = memo((props) => {
  const { onResize, width, ...restProps } = props;
  if (!width) {
    return <th {...restProps} />;
  }
  return (
    <Resizable
      width={width}
      height={0}
      handle={
        <span
          className="react-resizable-handle"
          onClick={(e) => {
            e.stopPropagation();
          }}
        />
      }
      onResize={onResize}
      draggableOpts={{ enableUserSelectHack: false }}
    >
      <th {...restProps} />
    </Resizable>
  );
});

export default ResizableTitle;

2. 最小化重新渲染

确保只有在列宽真正变化时才更新状态。可以通过 shouldComponentUpdateReact.memo 来优化渲染逻辑。

3. 使用虚拟化

对于非常大的数据集,可以考虑使用虚拟化技术(如 react-virtualizedreact-window)来提升表格的性能。虚拟化技术可以显著减少 DOM 节点的数量,提高渲染性能。

4. 性能监测和优化

使用 React 开发者工具和性能监测工具来识别性能瓶颈,并有针对性地优化。

改进后的 withResizableColumns 示例

综合考虑以上优化措施,这里是改进后的 withResizableColumns 示例:

jsx 复制代码
import React, { useState, useEffect, memo } from 'react';
import { Resizable } from 'react-resizable';
import { debounce } from 'lodash';
import styles from './index.module.less';

interface WithResizableColumnsProps {
  columns: any[];
}

const ResizableTitle = memo((props: {
  onResize: (e: React.SyntheticEvent<Element>, data: ResizeCallbackData) => void;
  width: number;
}) => {
  const { onResize, width, ...restProps } = props;
  if (!width) {
    return <th {...restProps} />;
  }
  return (
    <Resizable
      width={width}
      height={0}
      handle={
        <span
          className="react-resizable-handle"
          onClick={(e) => {
            e.stopPropagation();
          }}
        />
      }
      onResize={onResize}
      draggableOpts={{ enableUserSelectHack: false }}
    >
      <th {...restProps} />
    </Resizable>
  );
});

const withResizableColumns = (WrappedComponent: React.ComponentType<any>) => {
  return (props: WithResizableColumnsProps) => {
    const { columns: initialColumns } = props;
    const [columns, setColumns] = useState(initialColumns);

    useEffect(() => {
      setColumns(initialColumns);
    }, [initialColumns]);

    const handleResize = (index: number) => debounce((_, { size }) => {
      const newColumns = [...columns];
      newColumns[index] = {
        ...newColumns[index],
        width: size.width,
      };
      setColumns(newColumns);
    }, 100); // 防抖处理

    const mergedColumns = columns.map((col, index) => ({
      ...col,
      onHeaderCell: (column: any) => ({
        width: column.width,
        onResize: handleResize(index) as React.ReactEventHandler<any>,
      }),
    }));

    return (
      <WrappedComponent
        {...props}
        columns={mergedColumns}
        components={{
          header: {
            cell: ResizableTitle,
          },
        }}
        className={styles.resizableTableContainer}
      />
    );
  };
};

export default withResizableColumns;

总结

通过使用 React.memo、防抖处理、最小化重新渲染、虚拟化以及性能监测等优化技术,可以显著提高列宽调整操作的性能和流畅度。确保高阶组件和表格组件在处理复杂场景时表现良好。

相关推荐
青年夏日科技工作者7 分钟前
虚幻浏览器插件 UE与JS通信
前端·javascript·html
雷神乐乐1 小时前
创建前端项目的方法
前端·javascript·vue.js
prince_zxill1 小时前
JavaScript面向对象编程:Prototype与Class的对比详解
前端·javascript·ecmascript·原型模式
D.eL2 小时前
Vue 2 项目中 Mock.js 的完整集成与使用教程
前端·javascript·vue.js
brzhang2 小时前
墙裂推荐一个在 Apple Silicon 上创建和管理虚拟机的轻量级开源工具:lume
前端·后端
Along丶WG2 小时前
解决国内服务器 npm install 卡住的问题
前端·npm·node.js
prince_zxill3 小时前
Node.js 和 npm 安装教程
前端·javascript·vue.js·npm·node.js
弄不死的强仔3 小时前
可被electron等调用的Qt截图-录屏工具【源码开放】
前端·javascript·qt·electron·贴图·qt5
霸王蟹4 小时前
el-table组件样式如何二次修改?
前端·javascript·vue.js·笔记·学习·前端框架
star010-5 小时前
一文学会HTML编程之视频+图文详解详析
前端·网络·网络安全·html·html5