目前,TypeScrip的control flow anlaysis不影响generic个类型参数.如果具有泛型类型T
的值t
,则switch
/case
、if
/else
或?
/:
类型保护t
可以将t
的表观类型从T
缩小到其他值(例如,T & string
),但它不能对类型参数T
做任何事情.
这是有充分理由的:你不能就这么重新-constrain T
.比方说T extends A | B
,然后勾选isA(t)
,将t
缩小到A
(或T & A
).这not个意味着T extends A
个.T
可能是A
,也可能是完整的union type A | B
.我们已经为T
建立了新的lower bound,而不是新的upper bound.如果TypeScrip有一种方式来表达下限约束,就像microsoft/TypeScript#14520中所要求的那样,那么可能会写成T super A extends A | B
.但它没有,所以我们有点被困住了,直到它得到实施.
你可能会认为T extends A | B
的意图是T
应该是exactly one of 103 or 104,而这些可能性的并集应该从一开始就被排除在考虑之外.那会很好,但这不是extends
的意思.我们需要一种新的限制,就像在microsoft/TypeScript#27808中所要求的那样.我们就叫它oneof
吧.然后,您可以编写T oneof A | B
,而判断isA(t)
确实会将T
重新约束为T oneof A
.但同样,这目前是不可能的,所以我们必须等待或解决它.
到目前为止,最简单的解决方法是使用type assertions来声明isA(t)
确实意味着T
是A
.
但您会问"这是消除编译器错误的唯一方法吗?"这个问题的答案是"不,但你可能无论如何都应该这么做".
现在,如果您想要使用泛型并拥有不带类型断言的类依赖类型,则需要进行重构,以便它不使用控制流分析,而是执行TypeScrip可以验证的为数不多的泛型操作之一.这主要涉及将具有泛型键类型的indexing转换为某个对象类型,或将mapped types转换为该对象类型.microsoft/TypeScript#47109中概述了一般方法.
不幸的是,对于您来说,重构是相当糟糕和引人注目的.您不能索引具有boolean
个值的对象,因此我们需要改为某种字符串/数字/枚举类型:
enum Bool { FALSE, TRUE }
const _true = Bool.TRUE;
const _false = Bool.FALSE;
然后,您需要重写类型以始终涉及使用这些伪布尔键索引到对象上的显式映射类型.也许是这样的:
interface BoolMap<T, F> {
[_true]: T,
[_false]: F
}
type Either<T, F, B extends Bool> = { [P in B]:
{ success: P } & BoolMap<{ result: T }, { cause: F }>[P]
}[B]
function either<T, F, TO, FO, B extends Bool>(
onSuccess: (i: T) => TO,
onFailure: (i: F) => FO,
input: Either<T, F, B>
) {
const func: { [P in Bool]:
(i: Either<T, F, P>) => BoolMap<TO, FO>[P]
} = {
[_true]: v => onSuccess(v.result),
[_false]: v => onFailure(v.cause)
};
return func[input.success](input);
}
真恶心.但至少它编译时没有错误,也没有类型断言.让我们对其进行测试,以确保其仍按预期运行:
const succ = (x: string) => x.length;
const fail = (x: number) => x.toFixed(2);
const r1 = either(succ, fail, { success: _true, result: "abc" });
// const r1: number
const r2 = either(succ, fail, { success: _false, cause: 404 });
// const r2: string
const r3 = either(succ, fail,
Math.random() < 0.5 ?
{ success: _true, result: "abc" } :
{ success: _false, cause: 404 }
);
// const r3: string | number
看起来不错,所以either
人中至少有callers人有同样的经历,呃,如果你忽略布尔的东西.
这就对了.就我个人而言,我可能只会坚持类型断言,也许在future 的TypeScrip版本中,当控制流分析影响泛型类型参数时,您可以删除它们.
Playground link to code个