目前,generics和narrowing在打印脚本中并不能很好地协同工作.当您选中is(this, 'foo')
时,type guard function可以将外观类型this
缩小到this & MyClass<"foo">
,但它对泛型类型参数T
没有任何影响.你可能会期望或希望T
会从Type
降到"foo"
,但事实并非如此.这意味着{foo: 123}
不能被赋值给Res[T]
,你会得到一个错误.
请注意,虽然this.type === "foo"
应该意味着T
等于"foo"
似乎是"显而易见的",但在一般情况下这样做实际上是不正确的.这是因为T
might是由完整的union type "foo" | "bar"
指定的,而建立this.type === "foo"
并不能消除这种可能性.充其量你可以说你知道T
和"foo"
一定有一些重叠,所以T & "foo"
不是never
,但这是一种很难实施或表达的约束.
有几个开放的功能请求要求通过控制流分析重新约束泛型类型参数.与此代码示例最相关的是microsoft/TypeScript#27808和microsoft/TypeScript#33014,前者允许您强制T
仅为one of、"foo"
和"bar"
,而不是联合;后者将使编译器看到,即使T
是完整的联合,返回{foo: 123}
也是安全的.这两种方法中的任何一种都可能使代码按原样工作,但目前它们不是语言的一部分,您需要解决它.
最简单的解决方法是使用type assertions来告诉编译器不要担心验证类型.例如:
public run(): Res[T] {
if (is(this, 'foo')) {
return { foo: 123 } as Res[T]; // okay
}
return { bar: 'xyz' } as Res[T]; // okay
}
对于您来说,这是最便捷的方式,只需对代码进行最少的更改.但请注意,在这里,您正在将类型判断的责任从编译器手中移开.您可以将上面的判断更改为(!is(this, 'foo'))
,编译器仍然会对它感到满意.因此,在进行类型断言以证明您所说的是事实时,您必须小心.
如果这里更重要的是从编译器获得一些类型安全保证,那么解决方法将涉及重构,从类型保护和控制流分析转向泛型indexing.您想要的返回类型是indexed access type Res[T]
,因此如果您希望编译器理解这一点,您可以使用类型T
的键索引到类型Res
的对象,并返回该对象:
public run(): Res[T] {
return {
foo: { foo: 123 },
bar: { bar: 'xyz' }
}[this.type]; // okay
}
这可能看起来很奇怪,但它类似于判断this.type
,然后根据结果 bootstrap 控制流的逻辑.这里唯一的主要区别是运行库实际上将在每次调用run()
时同时计算可能的返回值{foo: 123}
和{bar: 'xyz'}
,即使它丢弃了其中的一个.对于没有这种副作用的小对象文字来说,这可能无关紧要.但是,如果您确实希望拥有与前面相同的行为,则可以使用getters来确保只执行与实际this.type
值对应的代码块:
public run(): Res[T] {
return {
get foo() { return { foo: 123 } },
get bar() { return { bar: 'xyz' } }
}[this.type]; // okay
}
现在idthis.type
是"foo"
,return {foo: 123}
将运行,而return {bar: 'xyz'}
不会.
您可以修改此实现以执行其他操作,但基本方法是通过实际的索引操作实现索引访问类型,而不是通过if
/else
语句.
这就对了.该语言还没有准备好同时使用泛型和类型保护,您可以通过放松类型判断或使用不带类型保护的泛型来解决这个问题.
Playground link to code个