TS 类型防护带复杂类型
TS type guards with complex type
我遇到了一个有趣的TS类型守卫行为,想问问你是我只能接受还是我做错了什么。我把最小的例子放在一起(嗯,接近最小)。
enum LegType {
LegA = 'LegA',
LegB = 'LegB',
}
interface LegA {
type: LegType.LegA
foo: string
}
interface LegB {
type: LegType.LegB
bar: string
}
type Leg = LegA | LegB
enum PlanType {
T = 'T',
C = 'C',
}
interface TPlan {
type: PlanType.T
legs: LegA[]
}
interface CPlan {
type: PlanType.C
legs: (LegA | LegB)[]
}
type Plan = TPlan | CPlan
const isLegA = (o: Leg): o is LegA => o.type === LegType.LegA
const getFoo = (plan: Plan): string | undefined => {
// const legs: Leg[] = plan.legs // With this line the code builds with no errors.
const legs = plan.legs // With this line the code contains error, the type of `legs` is resolved as: LegA[] | (LegA | LegB)[]
const someLegA = legs.find(isLegA)
return someLegA?.foo // Property 'foo' does not exist on type 'LegA | LegB'.
}
为什么需要添加 :Leg[]
?是否可以重写代码以便 TS 自己理解正确的类型是什么?
糟糕,我明白发生了什么,我认为这里唯一合理的答案是做你所做的:将 legs
注释为 Leg[]
。
所以,library type signature for Array
's find()
method is overloaded and the overload that accepts a user-defined type guard callback and returns a narrowed element type is generic:
find<S extends T>(
predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any
): S | undefined;
find(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined;
当您不注释 legs
时,它会得到 union type Array<Leg> | Array<LegA>
。这种类型被认为与 Array<Leg>
兼容,但它不会自动将其视为兼容(这可能很好。Array<LegA>
不应该让你 push()
一个 LegB
,但 Array<Leg>
应该)。
这意味着 legs.find
的类型本身就是一个 union 其签名不相同的函数:
type LegsFind = {
<S extends LegA>(p: (this: void, v: LegA, i: number, o: LegA[]) => v is S, t?: any): S | undefined;
(p: (v: LegA, i: number, o: LegA[]) => unknown, t?: any): LegA | undefined;
} | {
<S extends Leg>(p: (this: void, v: Leg, i: number, o: Leg[]) => v is S, t?: any): S | undefined;
(p: (v: Leg, i: number, o: Leg[]) => unknown, t?: any): Leg | undefined;
};
在 TypeScript 3.3 之前,编译器会直接放弃并告诉您它不知道如何调用这样的函数联合。如果您正在调用 find()
等非变异方法,有人会告诉您将 Array<X> | Array<Y>
更改为 Array<X | Y>
,然后您将使用 Leg[]
并继续。
引入了 TypeScript 3.3 improved behavior for calling union types. There are cases when the compiler can take a union of functions and represent it as a single function which takes an intersection of the parameters from each member of the union. You can read more about it in the relevant pull request, microsoft/TypeScript#29011。
但是,there are cases when it can't do this:特别是联合的多个分支是重载函数或泛型函数。不幸的是 find()
,这就是你所处的情况。看起来编译器 尝试 想出一种方法使这个可调用,但它放弃了通用签名对应于 user-defined-type-guard 缩小并以 "regular" 结束,可能是因为它们彼此兼容:
type LegsFind33 = (p: (v: LegA, i: number, o: Leg[]) => unknown, thisArg?: any) => Leg | undefined;
所以你调用成功了,没有发生缩小,你运行进入了这里的问题。调用函数并集的问题没有完全解决;它的 GitHub 问题 microsoft/TypeScript#7294 已关闭并标记为 "Revisit"。
所以在重新讨论之前,这里的建议是 仍然 对待 Array<X> | Array<Y>
像 Array<X | Y>
如果你调用非变异方法,比如 find()
... 使用 Leg[]
并继续前进。
好的,希望对您有所帮助;祝你好运!
我遇到了一个有趣的TS类型守卫行为,想问问你是我只能接受还是我做错了什么。我把最小的例子放在一起(嗯,接近最小)。
enum LegType {
LegA = 'LegA',
LegB = 'LegB',
}
interface LegA {
type: LegType.LegA
foo: string
}
interface LegB {
type: LegType.LegB
bar: string
}
type Leg = LegA | LegB
enum PlanType {
T = 'T',
C = 'C',
}
interface TPlan {
type: PlanType.T
legs: LegA[]
}
interface CPlan {
type: PlanType.C
legs: (LegA | LegB)[]
}
type Plan = TPlan | CPlan
const isLegA = (o: Leg): o is LegA => o.type === LegType.LegA
const getFoo = (plan: Plan): string | undefined => {
// const legs: Leg[] = plan.legs // With this line the code builds with no errors.
const legs = plan.legs // With this line the code contains error, the type of `legs` is resolved as: LegA[] | (LegA | LegB)[]
const someLegA = legs.find(isLegA)
return someLegA?.foo // Property 'foo' does not exist on type 'LegA | LegB'.
}
为什么需要添加 :Leg[]
?是否可以重写代码以便 TS 自己理解正确的类型是什么?
糟糕,我明白发生了什么,我认为这里唯一合理的答案是做你所做的:将 legs
注释为 Leg[]
。
所以,library type signature for Array
's find()
method is overloaded and the overload that accepts a user-defined type guard callback and returns a narrowed element type is generic:
find<S extends T>( predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any ): S | undefined; find(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined;
当您不注释 legs
时,它会得到 union type Array<Leg> | Array<LegA>
。这种类型被认为与 Array<Leg>
兼容,但它不会自动将其视为兼容(这可能很好。Array<LegA>
不应该让你 push()
一个 LegB
,但 Array<Leg>
应该)。
这意味着 legs.find
的类型本身就是一个 union 其签名不相同的函数:
type LegsFind = {
<S extends LegA>(p: (this: void, v: LegA, i: number, o: LegA[]) => v is S, t?: any): S | undefined;
(p: (v: LegA, i: number, o: LegA[]) => unknown, t?: any): LegA | undefined;
} | {
<S extends Leg>(p: (this: void, v: Leg, i: number, o: Leg[]) => v is S, t?: any): S | undefined;
(p: (v: Leg, i: number, o: Leg[]) => unknown, t?: any): Leg | undefined;
};
在 TypeScript 3.3 之前,编译器会直接放弃并告诉您它不知道如何调用这样的函数联合。如果您正在调用 find()
等非变异方法,有人会告诉您将 Array<X> | Array<Y>
更改为 Array<X | Y>
,然后您将使用 Leg[]
并继续。
引入了 TypeScript 3.3 improved behavior for calling union types. There are cases when the compiler can take a union of functions and represent it as a single function which takes an intersection of the parameters from each member of the union. You can read more about it in the relevant pull request, microsoft/TypeScript#29011。
但是,there are cases when it can't do this:特别是联合的多个分支是重载函数或泛型函数。不幸的是 find()
,这就是你所处的情况。看起来编译器 尝试 想出一种方法使这个可调用,但它放弃了通用签名对应于 user-defined-type-guard 缩小并以 "regular" 结束,可能是因为它们彼此兼容:
type LegsFind33 = (p: (v: LegA, i: number, o: Leg[]) => unknown, thisArg?: any) => Leg | undefined;
所以你调用成功了,没有发生缩小,你运行进入了这里的问题。调用函数并集的问题没有完全解决;它的 GitHub 问题 microsoft/TypeScript#7294 已关闭并标记为 "Revisit"。
所以在重新讨论之前,这里的建议是 仍然 对待 Array<X> | Array<Y>
像 Array<X | Y>
如果你调用非变异方法,比如 find()
... 使用 Leg[]
并继续前进。
好的,希望对您有所帮助;祝你好运!