首页 > 解决方案 > 如何告诉 TypeScript 只允许具有字符串值的属性作为函数的参数

问题描述

我想编写一个泛型函数,将泛型类型的属性名称作为参数。我需要让 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]]'
}

我能做些什么来让编译器明白它应该只接受字符串属性名称并且值始终是 type string

标签: typescriptgenerics

解决方案


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"); 
}

你会注意到我也保留了你的签名,一个对调用站点上的智能感知表现更好,新签名对实现表现更好。

根据您在函数中执行多少索引,您可能最好只使用断言:

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"); 
}

推荐阅读