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、防抖处理、最小化重新渲染、虚拟化以及性能监测等优化技术,可以显著提高列宽调整操作的性能和流畅度。确保高阶组件和表格组件在处理复杂场景时表现良好。

相关推荐
用户99045017780095 小时前
学习了AI修图,我把自己闲鱼出租房照片整成airbnb风格了
前端
kyriewen6 小时前
白宫直接给 OpenAI 下了限制令,GPT-5.6 不能随便放出来了
前端·javascript·面试
PedroQue997 小时前
Vite插件v0.2.6:架构优化与自动化升级
前端·vite
threerocks9 小时前
什么?我连 A2A、MCP 都没学会,现在又来了 AG-UI、A2UI.
前端·aigc·ai编程
牛奶9 小时前
如何自己写一个浏览器插件?
前端·chrome·浏览器
亿元程序员10 小时前
为什么Cocos都4.0了还有人用2.x?
前端
MomentYY10 小时前
AI 到底是“懂”,还是在“猜”?
前端·人工智能·ai编程
鹏毓网络科技10 小时前
Cursor Rules 文件配置实战:3 个隐藏参数让我每月少写 40% 样板代码
前端·github
没烦恼30110 小时前
无痕模式下 HTTP\-First 拦截引发的“页面刷新”误判
前端
ZhengEnCi10 小时前
Q02-Vue-React-index.html完全指南
vue.js·react.js·html