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

相关推荐
java_heartLake3 分钟前
Vue3之性能优化
javascript·vue.js·性能优化
Swift社区6 分钟前
HarmonyOS 实践 - 设计模式在代码中的作用
javascript
哑巴语天雨27 分钟前
React+Vite项目框架
前端·react.js·前端框架
初遇你时动了情40 分钟前
react 项目打包二级目 使用BrowserRouter 解决页面刷新404 找不到路由
前端·javascript·react.js
乔峰不是张无忌3301 小时前
【HTML】动态闪烁圣诞树+雪花+音效
前端·javascript·html·圣诞树
码农老起1 小时前
掌握 React:组件化开发与性能优化的实战指南
react.js·前端框架
鸿蒙自习室1 小时前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript
m0_748250741 小时前
高性能Web网关:OpenResty 基础讲解
前端·openresty
前端没钱2 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
汪洪墩2 小时前
【Mars3d】设置backgroundImage、map.scene.skyBox、backgroundImage来回切换
开发语言·javascript·python·ecmascript·webgl·cesium