目前,作为control flow analysis的结果,TypeScrip无法重新约束generic个类型参数.在func(type, attr)
的身体里,你判断type === Types.ME
.这可以将type
的类型从T
变为类似T & Types.ME
的类型.But it cannot do anything to 106 itself.类型参数T
顽固地保持不变;它不被限制为Types.ME
.因此编译器不能得出attr
是类型Attributes<Types.ME>
的结论.
编译器拒绝更改T
在技术上是正确的.这是因为,虽然像type
这样的单个值一次只能是一件事,但像T
这样的类型参数可以是union.事实上,您可以拨打func()
,T
等于完整的Types.ME | Types.YOU
联盟,如下所示:
func(
Math.random() < 0.999 ? Types.ME : Types.YOU,
{ hat: true }
) // compiles without error
如果判断一下,您会发现T
被推断为Types
(Types.ME | Types.YOU
的完全并集,因此,即使在type
是Types.ME
的99.9%的可能性的情况下,attr
也被允许为{hat: true}
.
在microsoft/TypeScript#27808有一个长期开放的功能请求,它要求一种方式来表示"T
将恰好是Types.ME
或Types.YOU
中的一个;它不能是一个联合".然后,也许在函数体内部,判断type === Types.ME
将允许T
本身被约束为Types.ME
,并且事情将按预期工作.而且,拨打Math.random() < 0.999
的电话大概会被拒绝.
但现在,它不是语言的一部分.
您可以考虑采用这样的方法,不是让func
成为泛型,而是使其类似于overloaded函数,其中Types
的每个成员都有一个调用签名.您可以将其编写为具有rest parameter的函数,其类型为tuple types的discriminated union,编译器将为treat it as such inside the function body.
也许是这样的:
type FuncArg =
[type: Types.ME, attr: { keys: true }] |
[type: Types.YOU, attr: { hat: true }];
const func: (...args: FuncArg) => void = (type, attr) => {
if (type === Types.ME) {
console.log(attr.keys) // this works
}
}
现在,您不能进行无效调用:
func(
Math.random() < 0.999 ? Types.ME : Types.YOU,
{ hat: true }
) // error, type 'Types.ME' is not assignable to type 'Types.YOU'
一切都是可行的,因为现在在FuncArg
中,type
和attr
被Bundle 在一起;这就像想象T
被约束为一次只是Types
的一个成员,并遍历各种可能性.请注意,如果需要,可以从问题中给出的Attributes
类型或从另一个映射接口计算FuncArg
,但这超出了问题的范围.
Playground link to code个