后台管理系统的列表页几乎都离不开分页:请求时要传页码、每页条数、排序等参数,响应时要拿到当前页数据列表和总数、总页数等元信息。把这些拆成"请求参数 / 分页元信息 / 分页数据 / 响应联合"四层类型,并用泛型参数化列表项,可以让任意实体列表(用户、订单、商品)复用同一套分页类型,杜绝字段拼写与类型错误。
// 分页类型封装:请求参数、响应数据与元信息的完整分页类型设计
// 泛型化列表项类型,覆盖分页查询的全套类型
// 1. 分页请求参数
interface PaginationParams {
page: number; // 当前页码,从 1 开始
pageSize: number; // 每页条数
sort?: string; // 可选排序字段
order?: 'asc' | 'desc'; // 可选排序方向
}
// 2. 分页元信息
interface PaginationMeta {
page: number;
pageSize: number;
total: number; // 总条数
totalPages: number; // 总页数
hasMore: boolean; // 是否还有下一页
}
// 3. 分页响应数据:泛型化列表项
interface PaginatedData<T> {
list: T[]; // 当前页数据
meta: PaginationMeta; // 分页元信息
}
// 4. 统一响应(成功/失败联合,data 泛型)
type ApiResponse<T> =
| { code: number; message: string; data: T; success: true }
| { code: number; message: string; data: null; success: false };
// 5. 分页 API 响应 = 统一响应 + 分页数据
type PaginatedResponse<T> = ApiResponse<PaginatedData<T>>;
interface User { id: number; name: string; }
// 应用:分页查询函数
async function fetchUsers(params: PaginationParams): Promise<PaginatedResponse<User>> {
const res = await fetch(`/api/users?page=${params.page}&size=${params.pageSize}`);
return res.json();
}
// 调用:用 success 收窄后访问列表与元信息
async function main() {
const res = await fetchUsers({ page: 1, pageSize: 10, order: 'desc' });
if (res.success) {
res.data.list.forEach(u => console.log(u.name)); // User[]
console.log(res.data.meta.total); // number
console.log(res.data.meta.hasMore); // boolean
}
}
PaginatedData<T>:list 类型为 T[],任意实体套上泛型即得到对应分页数据类型,避免每个列表重复定义。PaginatedResponse<T> = ApiResponse<PaginatedData<T>>,把分页数据塞进统一响应的 data,复用成功/失败联合,无需重复定义响应结构。order?: 'asc' | 'desc':排序方向用字面量联合约束,比 string 更安全,调用方传错值会被编译期拦截。// 不同实体复用同一套分页类型
type UserPage = PaginatedResponse<User>;
interface Order { id: number; amount: number; }
type OrderPage = PaginatedResponse<Order>;
// 成功时 data 的结构
type DataShape = PaginatedData<User>;
// => { list: User[]; meta: PaginationMeta }
// 元信息字段推导
type Meta = PaginationMeta;
// => { page: number; pageSize: number; total: number; totalPages: number; hasMore: boolean }
// 实战:无限滚动"加载更多"判断
function canLoadMore(res: PaginatedResponse<User>): boolean {
return res.success ? res.data.meta.hasMore : false;
}