TypeScript实现表格行列类型安全:列定义约束行结构
TypeScript实现表格行与列的类型安全约束
要让表格行数据严格匹配列定义的结构,同时支持行的额外属性、实现渲染函数的类型安全,核心是利用泛型将列的定义与行的类型强绑定,同时通过类型映射实现字段类型的对应。
1. 基础类型定义
先定义类型映射规则,将列的type字段转换为TypeScript原生类型,同时定义行的通用可选属性:
// 列类型到TS原生类型的映射 type ColumnTypeToTS<T extends "string" | "number" | "action"> = T extends "string" ? string : T extends "number" ? number : T extends "action" ? never : never; // 行的通用可选属性 interface TableRowProps { rowType?: "Basic" | "Master"; }
2. 带类型约束的列定义
将TableColumn改为泛型接口,让renderFunction的参数类型与列的type自动匹配:
// 单个列的泛型定义,T为列的类型 interface TableColumn<T extends "string" | "number" | "action"> { name: string; type: T; id: string; // renderFunction的参数类型自动匹配列的type对应的TS类型 renderFunction?: (data: ColumnTypeToTS<T>) => string; }
3. 表格Props的泛型绑定
定义泛型的PaginatedTableProps,让columns的类型直接决定rows的结构:
// 从列数组类型中提取行的schema类型 type ColumnsToRowSchema<Columns extends readonly TableColumn<any>[]> = { [K in Columns[number]["name"]]: ColumnTypeToTS<Extract<Columns[number], { name: K }>["type"]>; }; // 表格Props的泛型定义,Columns为列数组的具体类型 interface PaginatedTableProps<Columns extends readonly TableColumn<any>[]> { rows: Array<TableRowProps & ColumnsToRowSchema<Columns>>; columns: Columns; }
4. 使用示例与类型校验
现在使用时,TypeScript会自动根据columns的定义校验rows的结构,同时检查renderFunction的参数类型:
const props: PaginatedTableProps<[ TableColumn<"string">, TableColumn<"number">, TableColumn<"string"> ]> = { columns: [ { name: "f", type: "string", id: "1" }, { name: "count", type: "number", id: "2" }, { name: "z", type: "string", id: "3", renderFunction: (s: string) => s } // 类型匹配,合法 // 如果这里写renderFunction: (n: number) => n.toString(),会报错:参数类型不匹配 ], rows: [ { f: "foo", count: 12345, z: "bar" }, // 合法 { f: "zig", count: 6789 }, // ❌ 错误:缺少必填字段z { f: "bar", count: "123", z: "far" }, // ❌ 错误:count应为number类型,实际是string { f: "bas", count: 123, z: "asdf", rowType: "Basic" }, // 合法,包含可选属性 { f: "bas", count: 123, z: "asdf", rowType: "Expert" } // ❌ 错误:rowType只能是"Basic"或"Master" ] };
5. 简化泛型写法(可选)
如果不想手动指定泛型参数,可以写一个辅助函数让TypeScript自动推导列的类型:
function createTableProps<Columns extends readonly TableColumn<any>[]>(props: PaginatedTableProps<Columns>) { return props; } // 使用时无需手动指定泛型,自动推导 const props = createTableProps({ columns: [ { name: "f", type: "string", id: "1" }, { name: "count", type: "number", id: "2" }, { name: "z", type: "string", id: "3", renderFunction: (s) => s } // s的类型自动推导为string ], rows: [ { f: "foo", count: 12345, z: "bar" }, // 错误行同样会被自动校验 ] });
关键说明
- 泛型
Columns捕获了列数组的具体类型,包括每个列的name和type,确保行数据的字段名和类型完全匹配。 ColumnsToRowSchema通过映射类型,将列数组转换为行的对象类型,实现了列定义到行结构的自动转换。- 列的
renderFunction参数类型通过ColumnTypeToTS与列的type强绑定,避免了类型不匹配的问题。
内容的提问来源于stack exchange,提问作者Pascal
相关产品推荐
相关产品推荐

