如何告诉TypeScript仅允许将具有字符串值的属性作为函数的参数

时间:2019-10-31 14:39:07

标签: typescript generics

我想编写一个通用函数,该函数以通用类型的属性名称作为参数。我需要使TypeScript断言该属性的值是特定类型的。考虑下面的简化代码:

interface MyObject {
 myProp: number;
 mySecondProp: string;
 myOtherProp: string;
 myFlagProp: boolean;
}

const doStuff<T> = (obj: T, propName: SomeType) => { /***/ }

我可以提取特定类型(例如string)的所有属性,但是编译器将无法确切知道该属性的类型:

type StringProps<T> = { 
  [K in keyof T]: T[K] extends string ? K : never }[keyof T] 
}

const doStuff<T> = (obj: T, propName: StringProps<T>) => { 
  obj[propName].indexOf("a"); // Property 'indexOf' does not exist on type 'T[{ [K in keyof T]: T[K] extends string ? K : never; }[keyof T]]'
}

如何使编译器理解它应该只接受字符串属性名称,并且该值始终为string类型?

1 个答案:

答案 0 :(得分:3)

Typescript通常不能很好地推断出仍然包含未解析类型参数的条件类型。因此,当您调用StringProps<T>可以轻松地解析为函数中T的任何字符串键时,编译器将不会尝试感知T[StringProps<T>]

您可以用其他方式指定参数。您可以将另一个类型参数用作属性键,并指定T必须是Record<K, string>。对于编译器来说,这样做更简单:

type StringProps<T> = { [K in keyof T]: T[K] extends string ? K : never }[keyof T];
function doStuff<T>(obj: T, propName: StringProps<T>): void
function doStuff<K extends PropertyKey, T extends Record<K, string>>(obj: T, propName: K): void { 
    obj[propName].indexOf("a"); 
}

Play

您会注意到,我也保留了您的签名,一个对呼叫站点的智能表现更好,对于新的实现,新的签名表现更好。

取决于您在函数中执行的索引编制数量,您最好只使用一个断言:

type StringProps<T> = { [K in keyof T]: T[K] extends string ? K : never }[keyof T];
function doStuff<T>(obj: T, propName: StringProps<T>): void{ 
    (obj[propName] as unknown as string).indexOf("a"); 
}