proTable--Request获取数据--actionRef 触发更新
'# proTable--Request获取数据--actionRef 触发更新
一、背景与问题
在Ant Design Pro的ProTable组件中,actionRef是一个核心特性,它允许开发者通过引用获取表格实例,从而实现对表格数据的动态控制。在实际开发中,我们常常需要根据外部事件(如按钮点击、搜索框输入、权限变更等)触发表格数据的重新获取。
传统做法是通过组件的ref直接操作DOM或调用方法,但这种方式存在以下问题:
- 无法在组件卸载后安全回收资源
- 无法处理复杂的数据更新逻辑
- 无法进行统一的错误处理和性能优化
而actionRef通过封装实例方法,提供了一套标准化的数据更新接口,同时支持分页、排序、过滤等参数的动态控制。
二、基本原理
ProTable的actionRef本质是一个React Ref,它指向组件实例的action对象。这个对象包含以下关键方法:
refresh():触发数据刷新reset():重置表格状态reload():重新加载数据(包含分页重置)
当调用refresh()时,ProTable会:
- 通过
request函数获取新的数据 - 自动处理分页参数(当前页码、每页数量)
- 更新表格数据和分页状态
- 触发
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: () => {
// 逻辑处理
},
}));关键逻辑包括:
- 参数处理:将
params与当前分页参数合并 - 调用
request函数获取数据 - 更新表格状态(数据、分页、加载状态)
- 触发
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. 性能优化策略
防抖处理:对频繁触发的搜索请求进行防抖
const debouncedRefresh = debounce(() => { tableRef.current?.refresh({ pageNum: 1, pageSize: 10, name: searchText, }); }, 500);缓存机制:对高频访问的接口进行本地缓存
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); }); };分页控制:避免不必要的数据获取
const handlePageChange = (current, size) => { if (current > 1) { tableRef.current?.refresh({ pageNum: current, pageSize: size, name: searchText, }); } else { // 保持当前页 } };
2. 安全考量
CSRF防护:在请求头中添加token
const request = async (params) => { const token = localStorage.getItem('token'); return fetch('/api/users', { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, }, params, }); };权限校验:在请求前检查用户权限
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,
});十、最佳实践
- 优先使用actionRef:当需要从外部控制数据刷新时,使用actionRef是最佳选择。
- 避免直接操作DOM:使用actionRef可以保证数据一致性,避免直接操作DOM带来的副作用。
- 合理使用分页参数:在刷新时重置分页参数,避免出现数据不一致的情况。
- 添加错误处理:在request函数中添加错误处理逻辑,避免程序崩溃。
- 结合缓存机制:对高频访问的接口进行缓存,提升性能。
- 安全校验:在请求前进行权限校验,确保数据安全性。
十一、总结
proTable的actionRef提供了一套完善的数据更新机制,使开发者能够灵活控制表格数据。通过refresh方法,可以实现分页、排序、过滤等参数的动态控制。在实际开发中,我们需要根据具体场景选择合适的使用方式:
- 应该使用:需要从外部控制数据刷新、需要动态传递参数、需要处理复杂筛选条件的场景
- 不应该使用:简单数据展示、不需要参数控制、数据更新逻辑简单的场景
通过合理使用actionRef,我们可以实现更健壮、更灵活的数据处理逻辑,同时避免常见的内存泄漏和数据不一致问题。在实际开发中,还需要结合缓存机制、安全校验、性能优化等技术,构建完整的解决方案。
评论已关闭