编译器正确地警告您,返回"foo"
或1
并不能保证满足generic返回类型T
.字符串和数字literal types存在,因此如果调用dummy(123)
,则T
将被推断为123
,并且返回值1
不能赋给123
.这可能会导致运行时错误,如下所示:
function dummy<T extends string | number>(input: T): T {
let result: T
if (typeof input === 'string') { result = 'foo' } else { result = 1 }
return result
}
const oops = dummy("abc");
// ^? const oops: "abc"
const obj = { abc: 123, def: 456 };
const num = obj[oops];
// ^? const num: number
num.toFixed() // no compiler error, but
// ? RUNTIME ERROR! num is undefined ?
在这里,编译器认为oops
是"abc"
类型的,这意味着它允许索引到键为"abc"
的对象,这会导致问题.
您可以通过几种方式修复您的类型,但编译器不能真正验证您的实现是否满足这些类型.
一种方法是将函数的返回类型从T
更改为conditional type,如果是T extends string
,则输出string
,否则将输出number
,这样即使T
比string
或number
窄,它仍然是正确的类型:
declare function dummy<T extends string | number>(
input: T
): T extends string ? string : number;
const str = dummy("abc");
// const str: string
const num = dummy(123);
// const num: number
现在,从呼叫方的Angular 来看,这是可行的.尽管如此,编译器仍然会抱怨实现,因为它不能遵循逻辑.有关更好地支持此功能的相关功能请求,请参见microsoft/TypeScript#33912.目前,您需要使用一些type assertions来取消警告:
function dummy<T extends string | number>(input: T): T extends string ? string : number {
let result: T extends string ? string : number;
if (typeof input === 'string') {
result = 'foo' as typeof result // <-- assert
} else {
result = 1 as typeof result // <-- assert
}
return result
}
另一种方法是使用多个调用签名来overload个函数:
declare function dummy(input: string): string;
declare function dummy(input: number): number;
const str = dummy("abc");
// const str: string
const num = dummy(123);
// const num: number
同样,从呼叫方的Angular 来看,这也是可行的.该实现不会给您带来编译器错误,但这是一种错误的安全感,因为编译器实际上并没有验证正确的输出与正确的输入:
function dummy(input: string): string;
function dummy(input: number): number;
function dummy(input: string | number) {
let result: string | number;
if (typeof input === 'string') {
result = 'foo' // okay, but result = 1 would also be "okay" here
} else {
result = 1 // okay, but result = "foo" would also be "okay" here
}
return result
}
您也可以使用这两种方法,使用单个通用重载调用签名:
// call signature is generic
function dummy<T extends string | number>(
input: T
): T extends string ? string : number;
// implementation is not
function dummy(input: string | number) {
let result: string | number;
if (typeof input === 'string') {
result = 'foo';
} else {
result = 1;
}
return result
}
与其他代码一样,这不是类型安全的(您可以将typeof input === 'string'
改为typeof input !== 'string'
,编译器不会注意到),但它可能会更方便.
Playground link to code个