You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中基于T[K]类型窄化并按需设置可选属性的问题

TypeScript 表格排序配置类型优化问题

需求

我要定义一个用于表格排序的配置接口,满足:

  • 包含两个核心属性:
    1. 泛型对象T的某个键K
    2. 接收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>[]) {}

存在的问题

  1. 联合类型污染:当T仅包含string和number类型时,sort函数的参数类型变成了所有T[K]的联合类型,而非当前sortField对应的具体类型
  2. 错误的强制要求:当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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 07:43:20