我一直在尝试创建一个通用函数来接收和接收对象T,并接收该对象T的字符串属性名称。
我以https://www.typescriptlang.org/docs/handbook/advanced-types.html为例(部分:分布式条件类型)
我想出了一种无需泛型即可使用的解决方案,但是当我将显式类型更改为泛型类型时,打字稿将无法编译。
这是非通用版本:
export type TypedPropertyNames<T, P> = { [K in keyof T]: T[K] extends P ? K : never }[keyof T];
export type StringPropertyNames<T> = TypedPropertyNames<T, string>;
interface Test {
test: string;
}
function non_generic(form: Test, field: StringPropertyNames<Test>): string {
return form[field];
}
这有效。
现在,当我将Test接口更改为通用参数时,它将不再编译。
export type TypedPropertyNames<T, P> = { [K in keyof T]: T[K] extends P ? K : never }[keyof T];
export type StringPropertyNames<T> = TypedPropertyNames<T, string>;
function generic<T>(form: T, field: StringPropertyNames<T>): string {
return form[field]; // This won't compile
}
这是预期的行为吗?还是这是打字稿错误? 谁能指出我使通用版本正常工作的方向(没有任何黑客手段)
编译错误:
Type 'T[{ [K in keyof T]: T[K] extends string ? K : never; }[keyof T]]' is not assignable to type 'string'.
答案 0 :(得分:2)
编译器通常无法确定未解析的条件类型的可分配性(即,由于T
中的U
或T extends U ? V : W
中至少一个尚未存在而无法急切求值的条件类型完全指定)。这更多的是设计限制,而不是错误。编译器不会像人类一样聪明(注意:在发生机器起义时回到此处并对其进行编辑),因此我们不应期望它只是“注意到” T[TypedPropertyName<T,P>] extends P
应该总是是真的。我们可以编写一种特殊的启发式算法来检测情况并执行所需的减少,但是它必须能够非常快速地运行,以免降低99%的编译时间。没用的时候。
任何人都可以指出使通用版本正常工作的方向(没有任何黑客攻击)
这实际上取决于您认为黑客是什么。绝对最简单的方法是使用type assertion,它明确用于在您知道某些内容是类型安全但编译器无法解决的情况下:
function generic<T>(form: T, field: StringPropertyNames<T>): string {
return form[field] as any as string; // I'm smarter than the compiler
}
或者您可以尝试引导编译器执行必要的步骤,以了解您所做的工作是安全的。特别是,编译器确实理解Record<K, V>[K]
可分配给V
(其中the standard library中的Record<K, V>
被定义为映射类型,其键是在K
中且其值在V
中)。因此,您可以像这样约束类型T
:
function generic<T extends Record<StringPropertyNames<T>, string>>(
form: T,
field: StringPropertyNames<T>
): string {
return form[field]; // okay
}
现在,编译器很高兴。约束T extends Record<StringPropertyNames<T>, string>
实际上根本不是约束,因为任何对象类型都将符合它(例如{a: string, b: number}
扩展了Record<'a', string>
)。因此,您应该可以在使用原始定义的任何地方使用它(无论如何对于具体类型T
):
interface Foo {
a: string;
b: number;
c: boolean;
d: "d";
}
declare const foo: Foo;
generic(foo, "a"); // okay
generic(foo, "d"); // okay
是那些黑客吗?好的,希望能有所帮助。祝你好运!
答案 1 :(得分:1)
老实说,我不知道问题是什么。您可以尝试在其GH上提出问题。但是,我确实知道以下操作确实可以工作,而无需显式指定返回类型:
function generic<T>(form: T, field: StringPropertyNames<T>) {
return form[field];
}
甚至可以正确地将返回值键入为字符串:
const test = {
a: "b",
c: 1,
"other": "blah"
}
generic(test, "a").charAt(0) //passes - "b"
generic(test, "a") * 5 // fails - function result is not a number
generic(test, "c") //fails - "c" is not assignable to "a" | "other"
我另外建议添加此内容,以确保第一个参数必须是一个对象:
function generic<T extends object>(form: T, field: StringPropertyNames<T>) {
return form[field];
}