如您所见,generic type parameter default 没有您想要的效果。如果您将类型称为Test 而不指定Test<string> 中的类型参数,编译器将使用默认值并将其解释为Test<any>。它不会为您推断string。有一个功能请求,microsoft/TypeScript#10671,要求提供一种方法让编译器推断未指定的类型参数......类似于Test<infer> 或Test<*>。但是目前 TypeScript 还没有这个功能。
您可以做的是创建一个通用实用程序函数来返回其输入:
const asTest = <T,>(test: Test<T>) => test;
当您调用泛型函数时,TypeScript 将尝试为测试参数推断出适当的类型。您可以调用此函数而不是注释变量:
const testString = asTest({
value1: "string1",
value2: "string2"
})
// const testString: Test<string>
const testStringNumber = asTest({
value1: "string1",
value2: 2 // error! 'number' is not assignable to 'string'
})
// const testString: Test<string>
这符合预期。
请注意,编译器是否会拒绝一个值或为它的类型参数合成一个union type 并不总是很明显。在testStringNumber 中,将T 推断为string | number 是合理的,但编译器使用的启发式方法是人们通常不想推断原始类型的联合。
另一方面,以下代码会导致推断出一个联合:
const testUnion = asTest({
value1: { a: "" },
value2: { b: "" }
})
/* Test<{
a: string;
b?: undefined;
} | {
b: string;
a?: undefined;
}> */
这种类型,Test<{a: string; b?: undefined} | {b: string; a?: undefined}> 确实准确地描述了传递给asTest() 的值的类型。编译器的启发式假设对象类型的联合更容易接受和预期。
但也许你更喜欢抱怨这种联合的东西,并且只使用(比如)value1 来推断T,然后只检查value2 反对它。也就是说,您希望value2 中的T 成为microsoft/TypeScript#14829 中要求的非推理类型参数用法。对此没有官方的直接支持,但有一些合理的方法可以模拟它。例如:
const asTest = <T, U extends T>(test: { value1: T, value2: U }): Test<T> => test;
这个版本的asTest 使用了两个泛型类型参数; T 对应于value1,U 对应于value2。 U 是constrained 可分配给T,因此编译器将只接受value2 具有可分配给value1 的类型的输入。并且编译器只会通过查看value1 而不是value2 来推断T。 testString 和 testStringNumber 的效果相同:
const testString = asTest({ value1: "string1", value2: "string2" });
// const testString: Test<string>
const testStringNumber = asTest({
value1: "string1",
value2: 2 // error! 'number' is not assignable to 'string'
})
// const testString: Test<string>
但现在testUnion 产生错误:
const testUnion = asTest({
value1: { a: "" },
value2: { b: "" } // error! '{ b: string; }' is not assignable to '{ a: string; }'
})
因为{b: ""} 不能分配给从value1 推断的{a: string} 类型。
Playground link to code