什么是前端分页?
定义
前端分页是指在浏览器端对已获取的完整数据集进行分页处理,所有的分页逻辑都在客户端执行。
工作原理
// 1. 一次性获取所有数据
const allFiles = await fetchAllFiles(); // 获取100个文件
// 2. 前端计算分页
const pageSize = 10;
const currentPage = 1;
const startIndex = (currentPage - 1) * pageSize; // 0
const endIndex = startIndex + pageSize; // 10
// 3. 创建当前页数据副本
const currentPageData = allFiles.slice(startIndex, endIndex); // 前10个文件
数据流程图
🌐 API调用: GET /api/files
↓
📦 返回完整数据集 [file1, file2, ..., file100]
↓
💻 前端存储: useState/useRequest
↓
🔢 分页计算: slice(startIndex, endIndex)
↓
📄 显示: 当前页数据 [file1-10]
↓
👆 用户点击下一页
↓
🔢 重新计算: slice(10, 20)
↓
📄 显示: [file11-20]
特点
- 一次性数据加载:首次请求获取全部数据
- 客户端分页逻辑:所有分页计算在浏览器完成
- 瞬时页面切换:无网络延迟
- 离线浏览支持:数据已在本地
什么是后端分页?
定义
后端分页是指在服务器端对数据进行分页处理,客户端每次只请求和接收当前页的数据。
工作原理
// 1. 请求特定页面数据
const requestPage1 = {
page: 1,
pageSize: 10,
filters: { fileType: 'PDF' }
};
const response = await fetch('/api/files/paginated', {
method: 'POST',
body: JSON.stringify(requestPage1)
});
// 2. 服务器返回当前页数据
const result = {
data: [file1, file2, ..., file10], // 只返回第1页的10个文件
total: 100, // 总数量
page: 1, // 当前页
pageSize: 10 // 每页大小
};
数据流程图
💻 用户请求第1页
↓
🌐 API调用: POST /api/files/paginated {page: 1, pageSize: 10}
↓
🗄️ 服务器查询: SELECT * FROM files LIMIT 10 OFFSET 0
↓
📦 返回第1页数据 [file1-10] + 元数据
↓
📄 显示: 当前页数据
↓
👆 用户点击下一页
↓
🌐 新的API调用: POST /api/files/paginated {page: 2, pageSize: 10}
↓
🗄️ 服务器查询: SELECT * FROM files LIMIT 10 OFFSET 10
↓
📦 返回第2页数据 [file11-20]
特点
- 按需数据加载:每次只获取当前页数据
- 服务器端分页逻辑:数据库层面的分页查询
- 网络请求延迟:每次翻页需要网络请求
- 内存使用最小:客户端只存储当前页数据
两者的区别和适用场景
核心区别对比
维度 | 前端分页 | 后端分页 |
---|---|---|
数据加载 | 一次性加载全部数据 | 按需加载当前页数据 |
--- | --- | --- |
网络请求 | 首次加载后无需网络请求 | 每次翻页都需要网络请求 |
响应速度 | 瞬时响应(< 16ms) | 网络延迟(100-500ms) |
内存占用 | 高(存储全部数据) | 低(只存储当前页) |
服务器压力 | 低(一次性查询) | 高(频繁查询) |
离线支持 | 支持 | 不支持 |
实时数据 | 不支持 | 支持 |
数据量适用标准
数据量级 | 推荐方案 | 内存占用 | 加载时间 | 用户体验 |
---|---|---|---|---|
< 100条 | 前端分页 | ~25KB | < 1秒 | 优秀 |
--- | --- | --- | --- | --- |
100-500条 | 前端分页 | ~125KB | 1-2秒 | 良好 |
500-1000条 | 前端分页 | ~250KB | 2-3秒 | 可接受 |
1000-5000条 | 后端分页 | ~10KB | 每页0.5秒 | 良好 |
> 5000条 | 后端分页 | ~10KB | 每页0.5秒 | 必须 |
业务场景适用性
前端分页适用场景
// 1. 文档管理系统
interface DocumentList {
documents: Document[]; // 通常 < 500个文档
totalSize: number;
}
// 2. 用户管理后台
interface UserList {
users: User[]; // 企业用户 < 1000个
departments: string[];
}
// 3. 配置项管理
interface ConfigList {
configs: Config[]; // 配置项 < 100个
categories: string[];
}
后端分页适用场景
// 1. 搜索结果页面
interface SearchResults {
results: SearchItem[]; // 可能数万条结果
facets: Facet[];
suggestions: string[];
}
// 2. 交易记录查询
interface TransactionList {
transactions: Transaction[]; // 历史交易可能数十万条
summary: TransactionSummary;
}
// 3. 日志查看系统
interface LogEntries {
logs: LogEntry[]; // 日志条目可能数百万条
filters: LogFilter[];
}
技术架构考虑
网络环境影响
// 高速稳定网络 - 两种方案都可行
const networkCondition = {
bandwidth: 'high', // > 10Mbps
latency: 'low', // < 50ms
stability: 'stable' // 99%+ 可用性
};
// 移动网络/不稳定网络 - 倾向前端分页
const mobileNetwork = {
bandwidth: 'limited', // 1-5Mbps
latency: 'high', // 100-300ms
stability: 'unstable' // 90-95% 可用性
};
数据更新频率
// 静态/低频更新数据 - 适合前端分页
interface StaticData {
updateFrequency: 'daily' | 'weekly' | 'monthly';
dataConsistency: 'eventual'; // 最终一致性可接受
}
// 实时/高频更新数据 - 适合后端分页
interface RealTimeData {
updateFrequency: 'realtime' | 'seconds' | 'minutes';
dataConsistency: 'strong'; // 需要强一致性
}
前端分页对内存的影响
什么是副本?
副本 = 原始数据的复制品
就像复印文件一样,副本是原始数据在内存中的复制品。
三种副本类型
筛选副本 (Filter Copy)
*// 原始数据: 100个文件*
const originalFiles = [file1, file2, ..., file100];
*// 筛选副本: 只要PDF文件*
const filteredFiles = originalFiles.filter(file =>
file.fileType === 'PDF'
); *// 结果: 30个PDF文件的新副本*
排序副本 (Sort Copy)
*// 基于筛选结果创建排序副本*
const sortedFiles = [...filteredFiles].sort((a, b) =>
a.fileName.localeCompare(b.fileName)
);*// 结果: 按名称排序的新副本*
分页副本 (Pagination Copy)
*// 基于排序结果创建分页副本*
const paginatedFiles = sortedFiles.slice(0, 10);*// 结果: 第1页的10个文件副本*
内存占用分析
单个文件项内存估算
interface FileItem {
fileId: string; *// ~10 bytes*
fileName: string; *// ~30 bytes*
fileType: string; *// ~25 bytes*
relevantTimePeriod: string; *// ~15 bytes*
sharedOn: string; *// ~25 bytes*
uploadedOn: string; *// ~25 bytes*
status: string; *// ~15 bytes*
fileSize: number; *// ~8 bytes*
}*// 总计: ~200-250 bytes/item*
不同数据量的内存占用:
100 items : ~25 KB
500 items : ~125 KB
1,000 items : ~250 KB
5,000 items: ~1.25 MB
副本生命周期
用户点击"下一页"
↓1. 新副本创建 (新内存区域)
↓2. 变量指向新副本
↓3. 旧副本失去引用
↓4. 垃圾回收器清理旧副本
内存占用时间线
正常: 2.5KB (单个副本)
峰值: 5KB (新旧副本短暂共存)
回归: 2.5KB (旧副本被清理)
实际案例分析
项目背景:VAMOS Business Performance Document Sharing Service
业务需求
- 功能:供应商共享文档的展示和管理
- 用户:内部员工和外部供应商
- 数据特征:文档数量相对固定,更新频率低
- 性能要求:快速响应,良好的用户体验
技术选型分析
1.数据量评估
// 实际数据分析
const businessContext = {
averageFilesPerVendorPerYear: 100+, // 每个供应商平均100+个文档每年
concurrentUsers: 1000, // 并发用户数
// 内存占用计算
memoryPerFile: 2.5, // KB
maxMemoryUsage: 200 * 2.5, // 50KB per vendor
totalMemoryImpact: 'minimal' // 对现代浏览器影响很小
};
2.选择前端分页的原因
const decisionFactors = {
dataSize: {
current: '< 200 files per request',
future: '< 5000 files per request',
verdict: '✅ 适合前端分页'
},
userExperience: {
requirement: '瞬时响应',
frontendPagination: '< 16ms',
backendPagination: '100-500ms',
verdict: '✅ 前端分页更优'
},
dataConsistency: {
requirement: '最终一致性可接受',
updateFrequency: 'low',
verdict: '✅ 前端分页可满足'
}
};
实现细节分析
1.核心代码
export const SharedFilesLandingPage = ({ vendorCode }) => {
// 1. 状态管理
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
// 2. 数据获取 - 一次性加载所有文件
const { data: filesData, loading, runAsync: loadFiles } = useListFiles();
// 3. 数据处理 - 当前实现只有分页副本
const filteredFiles = useMemo(() => {
return filesData?.files || [];
}, [filesData]);
// 4. 分页副本创建
const paginatedFiles = useMemo(() => {
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return filteredFiles.slice(startIndex, endIndex); // 创建分页副本
}, [filteredFiles, currentPage, pageSize]);
// 5. 副作用管理
useEffect(() => {
loadFiles({ vendorCode });
}, [vendorCode, loadFiles]);
};
2.内存使用分析
// 实际内存占用分析(基于200个文件)
const memoryAnalysis = {
originalData: {
fileCount: 200,
memoryPerFile: 250, // bytes
totalMemory: 50000, // 50KB
description: '后端返回的完整文件列表'
},
paginationCopy: {
fileCount: 10, // 每页显示10个
memoryPerFile: 250, // bytes
totalMemory: 2500, // 2.5KB
description: '当前页显示的文件副本'
},
reactOverhead: {
stateManagement: 1000, // 1KB (React状态)
componentTree: 2000, // 2KB (组件树)
eventHandlers: 500, // 0.5KB (事件处理)
description: 'React框架开销'
},
agGridOverhead: {
gridInstance: 5000, // 5KB (AG Grid实例)
virtualScrolling: 2000, // 2KB (虚拟滚动)
columnDefinitions: 1000, // 1KB (列定义)
description: 'AG Grid组件开销'
},
totalMemoryUsage: 64000, // 64KB
memoryEfficiency: '优秀' // 对于现代浏览器来说很小
};
最佳实践总结
技术选型决策树(rough)
const decisionTree = {
step1: {
question: '数据量是否 < 1000条?',
yes: 'step2',
no: 'step1b'
},
step1b: { // 中等数据量判断
question: '数据量是否 < 5000条?',
yes: 'step2', // 进入综合评估
no: 'backend_pagination' // 直接后端分页
},
step2: {
question: '是否需要实时数据?',
yes: 'backend_pagination',
no: 'step3'
},
step3: {
question: '是否需要复杂搜索?',
yes: 'backend_pagination',
no: 'step4'
},
step4: {
question: '用户体验是否优先?',
yes: 'frontend_pagination',
no: 'context_dependent'
}
};