proTable--Request获取数据--actionRef 触发更新

'# proTable--Request获取数据--actionRef 触发更新

一、背景与问题

在Ant Design Pro的ProTable组件中,actionRef是一个核心特性,它允许开发者通过引用获取表格实例,从而实现对表格数据的动态控制。在实际开发中,我们常常需要根据外部事件(如按钮点击、搜索框输入、权限变更等)触发表格数据的重新获取。

传统做法是通过组件的ref直接操作DOM或调用方法,但这种方式存在以下问题:

  • 无法在组件卸载后安全回收资源
  • 无法处理复杂的数据更新逻辑
  • 无法进行统一的错误处理和性能优化

而actionRef通过封装实例方法,提供了一套标准化的数据更新接口,同时支持分页、排序、过滤等参数的动态控制。

二、基本原理

ProTable的actionRef本质是一个React Ref,它指向组件实例的action对象。这个对象包含以下关键方法:

  • refresh():触发数据刷新
  • reset():重置表格状态
  • reload():重新加载数据(包含分页重置)

当调用refresh()时,ProTable会:

  1. 通过request函数获取新的数据
  2. 自动处理分页参数(当前页码、每页数量)
  3. 更新表格数据和分页状态
  4. 触发onReset和onLoad回调

三、环境准备

npm install @ant-design/pro-table

需要准备的开发环境:

  • React 18+(支持并发模式)
  • TypeScript(推荐)
  • Node.js 16+
  • Ant Design Pro 2.x+ 版本

四、核心实现

1. 基础用法:通过actionRef触发刷新

import { useRef } from 'react';
import { ProTable } from '@ant-design/pro-table';

const Demo: React.FC = () => {
  const tableRef = useRef();

  const columns = [
    { title: '名称', dataIndex: 'name' },
    { title: '年龄', dataIndex: 'age' },
  ];

  const request = async (params) => {
    console.log('请求参数:', params);
    // 模拟API请求
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({
          data: Array.from({ length: 10 }, (_, i) => ({
            key: i,
            name: `用户${i}`,
            age: 20 + Math.floor(Math.random() * 30),
          })),
          total: 100,
        });
      }, 500);
    });
  };

  return (
    <ProTable
      columns={columns}
      request={request}
      actionRef={tableRef}
      pagination={false}
    />
  );
};

关键代码解析:

  • actionRef={tableRef}:将ref绑定到组件实例
  • request函数:定义数据获取逻辑
  • tableRef.current?.refresh():调用refresh方法触发刷新

2. 带分页的刷新控制

import { useRef } from 'react';
import { ProTable } from '@ant-design/pro-table';

const Demo: React.FC = () => {
  const tableRef = useRef();

  const columns = [
    { title: '名称', dataIndex: 'name' },
    { title: '年龄', dataIndex: 'age' },
  ];

  const request = async (params) => {
    console.log('请求参数:', params);
    // 模拟分页查询
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({
          data: Array.from({ length: 10 }, (_, i) => ({
            key: i,
            name: `用户${i}`,
            age: 20 + Math.floor(Math.random() * 30),
          })),
          total: 100,
        });
      }, 500);
    });
  };

  const handleRefresh = () => {
    // 强制刷新到第一页
    tableRef.current?.refresh({
      pageNum: 1,
      pageSize: 10,
    });
  };

  return (
    <ProTable
      columns={columns}
      request={request}
      actionRef={tableRef}
      pagination={true}
    >
      <button onClick={handleRefresh}>刷新数据</button>
    </ProTable>
  );
};

关键代码解析:

  • 通过refresh方法控制分页参数
  • 可以传递pageNum和pageSize来精确控制分页
  • 按钮触发的刷新会覆盖当前页码

3. 带筛选条件的刷新

import { useRef, useState } from 'react';
import { ProTable } from '@ant-design/pro-table';

const Demo: React.FC = () => {
  const tableRef = useRef();
  const [searchText, setSearchText] = useState('');

  const columns = [
    { title: '名称', dataIndex: 'name' },
    { title: '年龄', dataIndex: 'age' },
  ];

  const request = async (params) => {
    console.log('请求参数:', params);
    // 模拟带筛选条件的查询
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({
          data: Array.from({ length: 10 }, (_, i) => ({
            key: i,
            name: `用户${i}`,
            age: 20 + Math.floor(Math.random() * 30),
          })),
          total: 100,
        });
      }, 500);
    });
  };

  const handleSearch = () => {
    // 通过actionRef传递筛选条件
    tableRef.current?.refresh({
      ...params,
      name: searchText,
    });
  };

  return (
    <ProTable
      columns={columns}
      request={request}
      actionRef={tableRef}
      pagination={true}
    >
      <input
        placeholder="输入搜索"
        value={searchText}
        onChange={(e) => setSearchText(e.target.value)}
      />
      <button onClick={handleSearch}>搜索</button>
    </ProTable>
  );
};

关键代码解析:

  • params是ProTable内部传递的参数对象
  • 可以在refresh中扩展参数
  • 通过actionRef控制参数传递

五、完整案例

用户管理案例:带搜索、刷新和分页控制

import { useRef, useState } from 'react';
import { ProTable } from '@ant-design/pro-table';

const UserManagement: React.FC = () => {
  const tableRef = useRef();
  const [searchText, setSearchText] = useState('');
  const [pageSize, setPageSize] = useState(10);

  const columns = [
    { title: '用户ID', dataIndex: 'id', valueType: 'index' },
    { title: '用户名', dataIndex: 'name' },
    { title: '年龄', dataIndex: 'age' },
    { title: '注册时间', dataIndex: 'registerTime', valueType: 'date' },
  ];

  const request = async (params) => {
    console.log('请求参数:', params);
    // 模拟API请求
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({
          data: Array.from({ length: 10 }, (_, i) => ({
            key: i,
            id: i + 1,
            name: `用户${i}`,
            age: 20 + Math.floor(Math.random() * 30),
            registerTime: new Date(Date.now() - i * 1000 * 60 * 60 * 24 * 30),
          })),
          total: 100,
        });
      }, 500);
    });
  };

  const handleRefresh = () => {
    // 强制刷新到第一页
    tableRef.current?.refresh({
      pageNum: 1,
      pageSize,
      name: searchText,
    });
  };

  const handlePageChange = (current, size) => {
    setPageSize(size);
    tableRef.current?.refresh({
      pageNum: current,
      pageSize: size,
      name: searchText,
    });
  };

  return (
    <ProTable
      columns={columns}
      request={request}
      actionRef={tableRef}
      pagination={{
        pageSize: pageSize,
        showSizeChanger: true,
        onChange: handlePageChange,
      }}
    >
      <div style={{ marginBottom: 16 }}>
        <input
          placeholder="输入搜索"
          value={searchText}
          onChange={(e) => setSearchText(e.target.value)}
        />
        <button onClick={handleRefresh} style={{ marginLeft: 8 }}>
          刷新数据
        </button>
      </div>
    </ProTable>
  );
};

完整案例包含:

  • 搜索功能
  • 分页控制
  • 自定义分页参数
  • 刷新按钮
  • 通过actionRef传递筛选条件

六、源码解析

ProTable的actionRef本质是通过useImperativeHandle暴露实例方法:

useImperativeHandle(ref, () => ({
  refresh: (params) => {
    // 逻辑处理
  },
  reset: () => {
    // 逻辑处理
  },
  reload: () => {
    // 逻辑处理
  },
}));

关键逻辑包括:

  1. 参数处理:将params与当前分页参数合并
  2. 调用request函数获取数据
  3. 更新表格状态(数据、分页、加载状态)
  4. 触发onLoad和onReset回调

七、进阶使用

1. 与权限系统结合

const handleRefresh = () => {
  if (hasPermission('refresh_data')) {
    tableRef.current?.refresh({
      pageNum: 1,
      pageSize: 10,
    });
  } else {
    message.error('无刷新数据权限');
  }
};

2. 与表单联动

const handleSearch = (values) => {
  tableRef.current?.refresh({
    ...values,
    pageNum: 1,
  });
};

3. 与缓存系统结合

const request = async (params) => {
  const cacheKey = `user_list_${params.pageNum}_${params.pageSize}`;
  const cached = localStorage.getItem(cacheKey);
  
  if (cached) {
    return JSON.parse(cached);
  }
  
  // 模拟网络请求
  return new Promise(resolve => {
    setTimeout(() => {
      const data = Array.from({ length: 10 }, (_, i) => ({
        key: i,
        id: i + 1,
        name: `用户${i}`,
        age: 20 + Math.floor(Math.random() * 30),
        registerTime: new Date(Date.now() - i * 1000 * 60 * 60 * 24 * 30),
      }));
      localStorage.setItem(cacheKey, JSON.stringify({
        data,
        total: 100,
      }));
      resolve({
        data,
        total: 100,
      });
    }, 500);
  });
};

八、性能与工程实践

1. 性能优化策略

  1. 防抖处理:对频繁触发的搜索请求进行防抖

    const debouncedRefresh = debounce(() => {
      tableRef.current?.refresh({
     pageNum: 1,
     pageSize: 10,
     name: searchText,
      });
    }, 500);
  2. 缓存机制:对高频访问的接口进行本地缓存

    const request = async (params) => {
      const cacheKey = `user_list_${params.pageNum}_${params.pageSize}`;
      const cached = localStorage.getItem(cacheKey);
      
      if (cached) {
     return JSON.parse(cached);
      }
      
      // 模拟网络请求
      return new Promise(resolve => {
     setTimeout(() => {
       const data = Array.from({ length: 10 }, (_, i) => ({
         key: i,
         id: i + 1,
         name: `用户${i}`,
         age: 20 + Math.floor(Math.random() * 30),
         registerTime: new Date(Date.now() - i * 1000 * 60 * 60 * 24 * 30),
       }));
       localStorage.setItem(cacheKey, JSON.stringify({
         data,
         total: 100,
       }));
       resolve({
         data,
         total: 100,
       });
     }, 500);
      });
    };
  3. 分页控制:避免不必要的数据获取

    const handlePageChange = (current, size) => {
      if (current > 1) {
     tableRef.current?.refresh({
       pageNum: current,
       pageSize: size,
       name: searchText,
     });
      } else {
     // 保持当前页
      }
    };

2. 安全考量

  1. CSRF防护:在请求头中添加token

    const request = async (params) => {
      const token = localStorage.getItem('token');
      
      return fetch('/api/users', {
     method: 'GET',
     headers: {
       'Authorization': `Bearer ${token}`,
     },
     params,
      });
    };
  2. 权限校验:在请求前检查用户权限

    const request = async (params) => {
      if (!hasPermission('read_user_data')) {
     throw new Error('无数据读取权限');
      }
      
      return new Promise(resolve => {
     setTimeout(() => {
       resolve({
         data: Array.from({ length: 10 }, (_, i) => ({
           key: i,
           id: i + 1,
           name: `用户${i}`,
           age: 20 + Math.floor(Math.random() * 30),
           registerTime: new Date(Date.now() - i * 1000 * 60 * 60 * 24 * 30),
         })),
         total: 100,
       });
     }, 500);
      });
    };

3. 方案比较

方案优点缺点
actionRef强大的控制能力,支持分页/排序/过滤需要维护引用
内置刷新按钮简单易用无法自定义刷新参数
通过ref直接调用方法灵活容易引发内存泄漏
状态管理库可集中管理状态增加复杂度

九、常见问题与踩坑

1. 常见错误

错误示例:

const tableRef = useRef();
// 未初始化时调用refresh
tableRef.current?.refresh();

错误原因: 组件未挂载时调用方法,可能导致空指针异常。

解决办法:

useEffect(() => {
  if (tableRef.current) {
    tableRef.current.refresh();
  }
}, []);

2. 参数传递错误

错误示例:

tableRef.current?.refresh({ page: 1 });

错误原因: 使用了不被支持的参数名称。

解决办法:

tableRef.current?.refresh({
  pageNum: 1,
  pageSize: 10,
});

3. 分页参数冲突

错误场景: 用户手动修改了分页参数,但刷新时未重置。

解决办法:

tableRef.current?.refresh({
  pageNum: 1,
  pageSize: 10,
  ...params,
});

十、最佳实践

  1. 优先使用actionRef:当需要从外部控制数据刷新时,使用actionRef是最佳选择。
  2. 避免直接操作DOM:使用actionRef可以保证数据一致性,避免直接操作DOM带来的副作用。
  3. 合理使用分页参数:在刷新时重置分页参数,避免出现数据不一致的情况。
  4. 添加错误处理:在request函数中添加错误处理逻辑,避免程序崩溃。
  5. 结合缓存机制:对高频访问的接口进行缓存,提升性能。
  6. 安全校验:在请求前进行权限校验,确保数据安全性。

十一、总结

proTable的actionRef提供了一套完善的数据更新机制,使开发者能够灵活控制表格数据。通过refresh方法,可以实现分页、排序、过滤等参数的动态控制。在实际开发中,我们需要根据具体场景选择合适的使用方式:

  • 应该使用:需要从外部控制数据刷新、需要动态传递参数、需要处理复杂筛选条件的场景
  • 不应该使用:简单数据展示、不需要参数控制、数据更新逻辑简单的场景

通过合理使用actionRef,我们可以实现更健壮、更灵活的数据处理逻辑,同时避免常见的内存泄漏和数据不一致问题。在实际开发中,还需要结合缓存机制、安全校验、性能优化等技术,构建完整的解决方案。

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日