TypeScript中基于T[K]类型窄化并按需设置可选属性的问题
TypeScript 表格排序配置类型优化问题
需求
我要定义一个用于表格排序的配置接口,满足:
- 包含两个核心属性:
- 泛型对象
T的某个键K - 接收
T[K]类型参数的排序函数(a: T[K], b: T[K]) => number
- 泛型对象
- 当
T[K]为string或number时,排序函数设为可选;其他类型必须强制提供排序函数
当前实现
interface BaseColumnSortProps<T extends object, K extends keyof T> { sortField: K; sort?: (a: T[K], b: T[K]) => number; } type ColumnSortProps<T extends object, K extends keyof T> = T[K] extends string | number ? BaseColumnSortProps<T, K> : // 字符串和数字类型无需强制sort Required<BaseColumnSortProps<T, K>>; // 其他类型需强制自定义sort interface ColumnData<T extends object> { sort?: ColumnSortProps<T, keyof T>; } function testFunction<T extends object>(config: ColumnData<T>[]) {}
存在的问题
- 联合类型污染:当
T仅包含string和number类型时,sort函数的参数类型变成了所有T[K]的联合类型,而非当前sortField对应的具体类型 - 错误的强制要求:当
T包含非string/number类型(如boolean)时,本该只对该类型的键强制要求sort属性,但现在所有键都被强制要求提供sort
问题示例
仅含字符串和数字的T
interface OnlyStringsAndNumbers { a: string; b: number; } testFunction<OnlyStringsAndNumbers>([ { sort: { sortField: "a" } }, // 正常,无错误 { sort: { sortField: "b", sort: (a, b) => a - b } } // 问题:a和b的类型为`string | number`,而非预期的`number` ])
含其他类型的T
interface OtherTypes { a: string; b: number; c: boolean; } testFunction<OtherTypes>([ { sort: { sortField: "a" }}, // 错误:不该要求sort,但现在报错 { sort: { sortField: "b" }}, // 同样错误,不该要求sort { sort: { sortField: "c" }} // 正确:必须要求sort,但现在前两个也报错 ])
解决方案
问题根源在于ColumnData<T>中直接使用ColumnSortProps<T, keyof T>,导致TypeScript将所有键的类型做了联合,而非针对每个键单独处理。我们需要用映射类型对每个键分别生成对应的配置类型,再通过联合类型提供选择:
// 针对单个键K的排序配置 type ColumnSortProps<T extends object, K extends keyof T> = T[K] extends string | number ? { sortField: K; sort?: (a: T[K], b: T[K]) => number } : { sortField: K; sort: (a: T[K], b: T[K]) => number }; // 生成所有键对应的配置类型的联合 type ColumnSortUnion<T extends object> = { [K in keyof T]: ColumnSortProps<T, K>; }[keyof T]; interface ColumnData<T extends object> { sort?: ColumnSortUnion<T>; } function testFunction<T extends object>(config: ColumnData<T>[]) {}
验证效果
仅含字符串和数字的T
interface OnlyStringsAndNumbers { a: string; b: number; } testFunction<OnlyStringsAndNumbers>([ { sort: { sortField: "a" } }, // 正常 { sort: { sortField: "b", sort: (a, b) => a - b } } // a和b的类型为`number`,符合预期 ])
含其他类型的T
interface OtherTypes { a: string; b: number; c: boolean; } testFunction<OtherTypes>([ { sort: { sortField: "a" }}, // 正常,无需sort { sort: { sortField: "b" }}, // 正常,无需sort { sort: { sortField: "c", sort: (a, b) => a ? 1 : -1 }} // 必须提供sort,符合预期 ])
原理说明
- 使用映射类型
{ [K in keyof T]: ColumnSortProps<T, K> }为每个键K单独生成对应的配置类型,避免了联合类型的污染 - 通过
[keyof T]将映射类型转换为所有单个键配置的联合类型,这样TypeScript就能根据sortField的具体值推断出对应的sort函数参数类型 - 对每个键单独判断
T[K]的类型,确保只有非string/number类型的键才强制要求sort属性
内容的提问来源于stack exchange,提问作者sookie
相关产品推荐
相关产品推荐

